10

在下面的程序中,我希望该任务得到 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 持有对 continuation 的引用,后者持有对第一个任务的引用。

更新

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);
}
4

2 回答 2

5

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

长答案:

没有被 GCed的原因Task是因为它可以像这样从 CTS 访问:ctsconttask。我认为这两个参考都不应该存在于您的案例中。

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

conttask引用在那里,因为实际上contContinuationTaskFromResultTask(一个派生自 的类Task)。这个类有一个字段保存前面的任务,当继续执行成功时它被清空,但在它被取消时不会。

于 2013-10-11T16:37:38.010 回答
-1

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

WeakReference weakTask = null;
using (var cts = new CancellationTokenSource())
{
  weakTask = Start(cts);
}

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Console.WriteLine(weakTask.IsAlive); // prints false
于 2013-10-11T07:06:26.647 回答