任务未垃圾收集 [英] Task not garbage collected
问题描述
在以下程序中,我希望任务能够获得GC,但事实并非如此。我使用了一个内存探查器,该探查器显示 CancellationTokenSource
拥有对它的引用,即使任务显然处于最终状态。如果删除 TaskContinuationOptions.OnlyOnRanToCompletion
,一切都会按预期进行。
In the following program, I'd expect the task to get GC'd, but it doesn't. I've used a memory profiler which showed that the CancellationTokenSource
holds a reference to it even though the task is clearly in a final state. If I remove TaskContinuationOptions.OnlyOnRanToCompletion
, everything works as expected.
为什么会发生,我该怎么办
Why does it happen and what can I do to prevent it?
static void Main()
{
var cts = new CancellationTokenSource();
var weakTask = Start(cts);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine(weakTask.IsAlive); // prints True
GC.KeepAlive(cts);
}
private static WeakReference Start(CancellationTokenSource cts)
{
var task = Task.Factory.StartNew(() => { throw new Exception(); });
var cont = task.ContinueWith(t => { }, cts.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
((IAsyncResult)cont).AsyncWaitHandle.WaitOne(); // prevents inlining of Task.Wait()
Console.WriteLine(task.Status); // Faulted
Console.WriteLine(cont.Status); // Canceled
return new WeakReference(task);
}
我的怀疑是因为连续性永远不会运行(它无法满足(在其选项中指定的标准),它永远不会从取消令牌中注销。因此,CTS保留了对延续性的引用,而延续性则保留了对第一个任务的引用。
My suspicion is that because the continuation never runs (it doesn't meet the criteria specified in its options), it never unregisters from the cancellation token. So the CTS holds a reference to the continuation, which holds a reference to the first task.
更新
PFX小组已确认这确实是泄漏。解决方法是,在使用取消令牌时,我们已停止使用任何延续条件。相反,我们总是执行延续,检查内部条件,如果不满足,则抛出 OperationCanceledException
。这保留了延续的语义。以下扩展方法对此进行了封装:
The PFX team has confirmed that this does appear to be a leak. As a workaround, we've stopped using any continuation conditions when using cancellation tokens. Instead, we always execute the continuation, check the condition inside, and throw an OperationCanceledException
if it is not met. This preserves the semantics of the continuation. The following extension method encapsulates this:
public static Task ContinueWith(this Task task, Func<TaskStatus, bool> predicate,
Action<Task> continuation, CancellationToken token)
{
return task.ContinueWith(t =>
{
if (predicate(t.Status))
continuation(t);
else
throw new OperationCanceledException();
}, token);
}
推荐答案
简短答案:我相信是一次内存泄漏(或两次,请参见下文),您应该报告它。
Short answer: I believe this is a memory leak (or two, see below) and you should report it.
长答案:
未对 Task
执行GC的原因是因为它可以通过CTS这样访问: cts
→ cont
→任务
。我认为这两个引用在您的案例中都不应该存在。
The reason why the Task
is not GCed is because it is reachable from the CTS like this: cts
→ cont
→ task
. I think both of those references should not exist in your case.
cts
→因为存在
引用,但从未取消注册。 cont
正确注册使用令牌进行取消的操作,所以存在cont 任务
正常完成时会注销,但取消时不会注销。我的猜测是错误的逻辑是,如果任务被取消,则无需取消注册,因为必须由取消导致任务被取消。
The cts
→ cont
reference is there because cont
correctly registers for cancellation using the token, but it never unregisters. It does unregister when a Task
completes normally, but not when it's canceled. My guess is the erroneous logic is that if the task was canceled, there is no need to unregister from the cancellation, because it had to be that cancellation that caused the task to be canceled.
存在续
→任务
引用,因为 cont
实际上是 ContinuationTaskFromResultTask
(从 Task
派生的类)。此类具有一个用于保存前一个任务的字段,当成功执行继续操作时该字段将消失,但在取消该操作时不会消失。
The cont
→ task
reference is there, because cont
is actually ContinuationTaskFromResultTask
(a class that derives from Task
). This class has a field that holds the antecedent task, which is nulled out when the continuation is executed successfully, but not when it's canceled.
这篇关于任务未垃圾收集的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!