【问题标题】:Task not garbage collected任务没有被垃圾收集
【发布时间】:2013-10-18 09:42:39
【问题描述】:

在下面的程序中,我希望该任务会被 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);
}

【问题讨论】:

  • 调试/发布模式?
  • 由GC完成时肯定还没有完成的任务复合。
  • 汉斯,不是。我正在使用弱参考。还有一个 WaitOne()。在尝试 GC 之前打印任务状态并出现故障。阅读代码:)
  • 我刚刚检查过...没有调用任务终结器...这很有趣。

标签: c# task-parallel-library .net-4.5


【解决方案1】:

简答:我认为这是内存泄漏(或两个,见下文),您应该report it。

长答案:

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

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

cont → task 引用在那里,因为cont 实际上是ContinuationTaskFromResultTask(派生自Task 的类)。该类有一个字段保存前面的任务,当继续执行成功时该字段为空,但在取消时不为空。

【讨论】:

  • 叹息 :) 我希望有更好的消息。我已经写信给 PFX 团队的成员,希望他们能确认这个错误。谢谢!
【解决方案2】:

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

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

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

Console.WriteLine(weakTask.IsAlive); // prints false

【讨论】:

  • 有道理。但这应该是评论,而不是答案。
  • 顺便说一句,在发布版本中甚至不需要using;如果您删除GC.KeepAlive,CTS 将被收集,所以难怪任务也会如此。但这根本对我的情况没有帮助。 CTS 长期存储在一个字段中。
  • "CTS长期保存在字段中。"为什么?
猜你喜欢
  • 1970-01-01
  • 2011-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多