任务不是垃圾收集

在下面的程序中,我希望任务能够得到GC,但事实并非如此。 我使用了一个内存分析器,它表明即使任务显然处于最终状态, CancellationTokenSource也会保存对它的引用。 如果我删除TaskContinuationOptions.OnlyOnRanToCompletion ,一切都按预期工作。

为什么会发生这种情况,我该怎么做才能防止它呢?

  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持有对延续的引用,该引用持有对第一个任务的引用。

更新

PFX团队已确认这似乎是泄漏。 作为解决方法,我们在使用取消令牌时已停止使用任何延续条件。 相反,我们总是执行continuation,检查内部条件,如果不满足则抛出OperationCanceledException 。 这保留了延续的语义。 以下扩展方法封装了这个:

 public static Task ContinueWith(this Task task, Func predicate, Action continuation, CancellationToken token) { return task.ContinueWith(t => { if (predicate(t.Status)) continuation(t); else throw new OperationCanceledException(); }, token); } 

简短的回答:我认为这是一个内存泄漏(或两个,见下文),你应该报告它 。

答案很长:

Task未被GC的原因是因为它可以从CTS到达: ctsconttask 。 我认为这两个引用都不应该存在于您的情况中。

ctscont引用是因为cont使用令牌正确注册取消,但它从不注销。 当Task正常完成时,它会取消注册,但在取消时则不会注销。 我的猜测是错误的逻辑是,如果任务被取消,则无需取消注册,因为必须是取消导致任务被取消。

conttask引用就在那里,因为cont实际上是ContinuationTaskFromResultTask (一个派生自Task的类)。 这个类有一个包含先行任务的字段,当成功执行延续时,该字段被清空,但是当它被取消时则不被清除。

作为补充…在这种情况下,正在调用终结器:

 WeakReference weakTask = null; using (var cts = new CancellationTokenSource()) { weakTask = Start(cts); } GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); Console.WriteLine(weakTask.IsAlive); // prints false