任务没有被垃圾收集 [英] Task not garbage collected

查看:23
本文介绍了任务没有被垃圾收集的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在下面的程序中,我希望任务能够被 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 持有对 continuation 的引用,它持有对第一个任务的引用.

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 团队已确认这确实是泄漏.作为一种解决方法,我们在使用取消令牌时已停止使用任何延续条件.相反,我们总是执行continuation,检查里面的条件,如果不满足则抛出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 不是 GCed 的原因是因为它可以像这样从 CTS 到达:ctscont任务.我认为在您的案例中不应该存在这两个参考文献.

The reason why the Task is not GCed is because it is reachable from the CTS like this: ctsconttask. I think both of those references should not exist in your case.

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

The ctscont 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.

conttask 引用就在那里,因为 cont 实际上是 ContinuationTaskFromResultTask(从任务).这个类有一个字段保存先行任务,当延续成功执行时,该字段被清零,但在取消时不会.

The conttask 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆