任务不是垃圾收集

Eli*_*bel 10 c# task-parallel-library .net-4.5

在下面的程序中,我希望任务能够获得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);
    }
Run Code Online (Sandbox Code Playgroud)

我怀疑是因为延续从不运行(它不符合其选项中指定的标准),所以它永远不会从取消令牌中取消注册.因此CTS持有对延续的引用,该引用持有对第一个任务的引用.

更新

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

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);
}
Run Code Online (Sandbox Code Playgroud)

svi*_*ick 5

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

答案很长:

之所以Task不GCed是因为它是可到达从CTS这样的:cts→交通cont→交通task.我认为这两个引用都不应该存在于你的情况中.

ctscont参考是那里,因为cont正确使用令牌注销登记,但它从来没有注销.它在Task正常完成时取消注册,但在取消时取消注册.我的猜测是错误的逻辑是,如果任务被取消,则无需取消注册,因为必须是取消导致任务被取消.

所述conttask参考是存在的,因为cont实际上是ContinuationTaskFromResultTask(从派生的类Task).这个类有一个包含先行任务的字段,当成功执行延续时,该字段被清空,但是当它被取消时则不被清除.