任务未垃圾收集 [英] Task not garbage collected

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

问题描述

在以下程序中,我希望任务能够获得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保留了对延续性的引用,而延续性则保留了对第一个任务的引用。

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小组已确认这确实是泄漏。解决方法是,在使用取消令牌时,我们已停止使用任何延续条件。相反,我们总是执行延续,检查内部条件,如果不满足,则抛出 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 执行GC的原因是因为它可以通过CTS这样访问: cts cont 任务。我认为这两个引用在您的案例中都不应该存在。

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.

cts 因为存在 cont 正确注册使用令牌进行取消的操作,所以存在cont 引用,但从未取消注册。 任务正常完成时会注销,但取消时不会注销。我的猜测是错误的逻辑是,如果任务被取消,则无需取消注册,因为必须由取消导致任务被取消。

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.

存在任务引用,因为 cont 实际上是 ContinuationTaskFromResultTask (从 Task 派生的类)。此类具有一个用于保存前一个任务的字段,当成功执行继续操作时该字段将消失,但在取消该操作时不会消失。

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天全站免登陆