我開發了一個實現工作項生產者/消費者模式的庫。工作被取消,每個出隊工作項目都會有一個單獨的任務,失敗和成功延續。.NET TPL CancellationToken內存泄漏
任務繼續在工作完成(或失敗)後重新排隊工作項目。
整個庫共享一箇中心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
我測量與螞蟻內存分析器內存使用,並看到了System.Threading.ExecutionContext,這要追溯到任務的延續和CancellationCallbackInfo
的大量增加。
正如你所看到的,我已經試圖處理使用取消標記的任務,但是這似乎沒有效果。
編輯
我使用.NET 4.0
更新
即使只是鏈接上失敗的延續的主要任務,內存使用持續上升。任務的繼續似乎阻止取消令牌註冊的註銷。
所以,如果一個任務與一個不能運行的延續鏈接(由於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中的一個錯誤。互斥條件下的任務取消不是這種情況下可能出現的情況。
您可以嘗試沒有聯鎖嗎? – i3arnon
在這個例子中,互鎖只存在於同步中 - 我想在測量存儲器之前等待所有的任務已經開始。刪除它不會改變任何東西。 – urbanhusky
你在等什麼? – i3arnon