gpt4 book ai didi

.NET TPL CancellationToken 内存泄漏

转载 作者:行者123 更新时间:2023-12-04 12:30:50 25 4
gpt4 key购买 nike

我开发了一个库,它为工作项实现了生产者/消费者模式。工作出队,并且为每个出队的工作项目启动了一个单独的任务,失败和成功的延续。

任务继续在完成(或失败)其工作后重新排队工作项。

全馆共用一个中央CancellationTokenSource ,这是在应用程序关闭时触发的。

我现在面临重大的内存泄漏。如果任务是使用取消标记作为参数创建的,那么任务似乎会保留在内存中,直到取消源被触发(然后被释放)。

这可以在此示例代码 (VB.NET) 中重现。主要任务是包装工作项的任务,继续任务将处理重新安排。

Dim oCancellationTokenSource As New CancellationTokenSource
Dim oToken As CancellationToken = oCancellationTokenSource.Token
Dim nActiveTasks As Integer = 0

Dim lBaseMemory As Long = GC.GetTotalMemory(True)

For iteration = 0 To 100 ' do this 101 times to see how much the memory increases

Dim lMemory As Long = GC.GetTotalMemory(True)

Console.WriteLine("Memory at iteration start: " & lMemory.ToString("N0"))
Console.WriteLine(" to baseline: " & (lMemory - lBaseMemory).ToString("N0"))

For i As Integer = 0 To 1000 ' 1001 iterations to get an immediate, measurable impact
Interlocked.Increment(nActiveTasks)
Dim outer As Integer = i
Dim oMainTask As New Task(Sub()
' perform some work
Interlocked.Decrement(nActiveTasks)
End Sub, oToken)
Dim inner As Integer = 1
Dim oFaulted As Task = oMainTask.ContinueWith(Sub()
Console.WriteLine("Failed " & outer & "." & inner)
' if failed, do something with the work and re-queue it, if possible
' (imagine code for re-queueing - essentially just a synchronized list.add)

' Does not help:
' oMainTask.Dispose()
End Sub, oToken, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default)
' if not using token, does not cause increase in memory:
'End Sub, TaskContinuationOptions.OnlyOnFaulted)

' Does not help:
' oFaulted.ContinueWith(Sub()
' oFaulted.Dispose()
' End Sub, TaskContinuationOptions.NotOnFaulted)


Dim oSucceeded As Task = oMainTask.ContinueWith(Sub()
' success
' re-queue for next iteration
' (imagine code for re-queueing - essentially just a synchronized list.add)

' Does not help:
' oMainTask.Dispose()
End Sub, oToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default)
' if not using token, does not cause increase in memory:
'End Sub, TaskContinuationOptions.OnlyOnRanToCompletion)

' Does not help:
' oSucceeded.ContinueWith(Sub()
' oSucceeded.Dispose()
' End Sub, TaskContinuationOptions.NotOnFaulted)


' This does not help either and makes processing much slower due to the thrown exception (at least one of these tasks is cancelled)
'Dim oDisposeTask As New Task(Sub()
' Try
' Task.WaitAll({oMainTask, oFaulted, oSucceeded, oFaultedFaulted, oSuccededFaulted})
' Catch ex As Exception

' End Try
' oMainTask.Dispose()
' oFaulted.Dispose()
' oSucceeded.Dispose()
' End Sub)

oMainTask.Start()
' oDisposeTask.Start()
Next

Console.WriteLine("Memory after creating tasks: " & GC.GetTotalMemory(True).ToString("N0"))

' Wait until all main tasks are finished (may not mean that continuations finished)

Dim previousActive As Integer = nActiveTasks
While nActiveTasks > 0
If previousActive <> nActiveTasks Then
Console.WriteLine("Active: " & nActiveTasks)
Thread.Sleep(500)
previousActive = nActiveTasks
End If

End While

Console.WriteLine("Memory after tasks finished: " & GC.GetTotalMemory(True).ToString("N0"))

Next

我使用 ANTS Memory Profiler 测量了内存使用情况,发现 System.Threading.ExecutionContext 大幅增加,这可以追溯到任务延续和 CancellationCallbackInfo .

如您所见,我已经尝试处理使用取消 token 的任务,但这似乎没有效果。

编辑

我正在使用 .NET 4.0

更新

即使只是将主要任务与失败的延续联系起来,内存使用量也会不断增加。任务继续似乎阻止从取消 token 注册中取消注册。

因此,如果任务与不运行的延续链接在一起(由于 TaskContinuationOptions),那么似乎存在内存泄漏。如果只有一个继续运行,那么我没有观察到内存泄漏。

解决方法

作为一种解决方法,我可以在没有任何 TaskContinuationOptions 的情况下进行单个延续。并在那里处理父任务的状态:
oMainTask.ContinueWith(Sub(t)
If t.IsCanceled Then
' ignore
ElseIf t.IsCompleted Then
' reschedule

ElseIf t.IsFaulted Then
' error handling

End If
End Sub)

在取消的情况下,我将不得不检查它的执行情况,但这似乎可以解决问题。我几乎怀疑 .NET Framework 中存在错误。具有互斥条件的任务取消并不罕见。

最佳答案

一些观察

  • 潜在的泄漏似乎仅在存在未运行的任务“分支”的情况下存在。在您的示例中,如果您注释掉 oFaulted任务,泄漏对我来说消失了。如果您更新代码以具有 oMainTask故障,使oFaulted任务运行并且 oSucceeded任务没有运行,然后注释掉 oSucceeded防止泄漏。
  • 也许没有帮助,但如果您调用 oCancellationTokenSource.Cancel()在所有任务运行后,内存释放。 Dispose 没有帮助,也没有 Dispose 取消源与任务的任何组合。
  • 我看了http://referencesource.microsoft.com/这是 4.5.2(有没有办法查看早期框架?)我知道它不一定相同,但它有助于了解正在发生的事情类型。基本上,当您将取消 token 传递给任务时,该任务会向取消 token 的取消源注册自己。所以取消源保存了对你所有任务的引用。我还不清楚为什么你的场景似乎正在泄漏。如果我有机会更深入地研究,我会更新,如果我发现任何东西。

  • 解决方法

    将您的分支逻辑移至始终运行的延续。
    Dim continuation As Task =
    oMainTask.ContinueWith(
    Sub(antecendent)
    If antecendent.Status = TaskStatus.Faulted Then
    'Handle errors
    ElseIf antecendent.Status = TaskStatus.RanToCompletion Then
    'Do something else
    End If
    End Sub,
    oToken,
    TaskContinuationOptions.None,
    TaskScheduler.Default)

    无论如何,这很有可能比其他方法更轻。在这两种情况下,一个延续总是运行,但使用此代码只创建 1 个延续任务而不是 2。

    关于.NET TPL CancellationToken 内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30029462/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com