Я разработал библиотеку, которая реализует шаблон производителя/потребителя для рабочих элементов. Работа отменяется, и отдельная задача с продолжением неудачи и успеха создается для каждой заданной рабочей позиции..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
Я измерил использование памяти с МУРАВЬЯМИ Memory Profiler и увидел большое увеличение System.Threading.ExecutionContext, берущего начало продолжений задачи и CancellationCallbackInfo
.
Как вы можете видеть, я уже пытался решить задачи, которые используют токен отмены, но это, похоже, не имеет никакого эффекта.
Редактировать
Я использую .NET 4.0
Update
Даже если только сцепление главную задачу с продолжением на провал, использование памяти постоянно растет. Продолжение задачи, по-видимому, препятствует снятию регистрации с регистрации маркера отмены.
Так что если задача связана цепью с продолжением, которое не работает (из-за 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