代码之家  ›  专栏  ›  技术社区  ›  Simon P Stevens

不在TPL任务对象上调用Dispose()是否可以接受?

  •  114
  • Simon P Stevens  · 技术社区  · 15 年前

    我想触发一个在后台线程上运行的任务。我不想等任务完成。

    在.NET 3.5中,我可以这样做:

    ThreadPool.QueueUserWorkItem(d => { DoSomething(); });
    

    在.NET 4中,TPL是建议的方法。我推荐的常见模式是:

    Task.Factory.StartNew(() => { DoSomething(); });
    

    但是, StartNew() 方法返回 Task 实现的对象 IDisposable 。这个 似乎被推荐这种模式的人忽视了。有关 Task.Dispose() 方法说明:

    “在释放对任务的最后一个引用之前,始终调用Dispose。”

    在任务完成之前,不能对其调用Dispose,因此让主线程等待并调用Dispose首先将无法在后台线程上执行此操作。似乎也没有任何可用于清理的已完成/已完成事件。

    任务类上的msdn页面对此没有评论,而“pro c 2010…”一书推荐了相同的模式,并且对任务处理没有评论。

    我知道如果我离开它,定稿器最终会抓住它,但当我做很多火和忘记这样的任务,定稿器线程会不知所措时,这会回来咬我吗?

    所以我的问题是:

    • 不打电话可以吗 Dispose() 任务 在这种情况下上课?如果是这样,为什么会有风险/后果?
    • 是否有文件对此进行讨论?
    • 或者是否有适当的方法处理 任务 我错过的东西?
    • 还是有其他方法可以使用TPL执行Fire&Forget任务?
    3 回复  |  直到 15 年前
        1
  •  99
  •   Peter Duniho    11 年前

    关于这个有个讨论 in the MSDN forums .

    微软PFX团队的成员史蒂芬·图布(Stephen Toub)对此表示:

    任务。由于任务存在释放 可能包装事件句柄 在等待任务时使用 完成,如果等待 线程实际上必须阻塞 与旋转或潜在的 正在执行它正在等待的任务)。 如果你所做的只是使用 继续,该事件句柄将 从未分配

    最好依靠最后定稿来解决问题。

    更新(2012年10月)
    史蒂芬·图布发表了一篇题为 Do I need to dispose of Tasks? 它给出了更多的细节,并解释了.NET 4.5中的改进。

    总之:你不需要处理 Task 对象99%的时间。

    处理对象有两个主要原因:以及时、确定的方式释放非托管资源,并避免运行对象的终结器的成本。这些都不适用于 任务 大多数时候:

    1. 从.NET 4.5开始,唯一一次 任务 分配内部等待句柄( 任务 对象)是显式使用 IAsyncResult.AsyncWaitHandle 任务 以及
    2. 这个 任务 对象本身没有终结器;句柄本身包装在带有终结器的对象中,因此除非已分配,否则没有要运行的终结器。
        2
  •  14
  •   Hans Passant    11 年前

    这与线程类的问题是相同的。它使用5个操作系统句柄,但不实现IDisposable。原始设计人员的良好决策,当然很少有合理的方法调用Dispose()方法。您必须先调用join()。

    任务类为此添加了一个句柄,一个内部手动重置事件。这是目前最便宜的操作系统资源。当然,它的dispose()方法只能释放一个事件句柄,而不能释放线程使用的5个句柄。 Yeah, don't bother .

    请注意,您应该对任务的IsFaulted属性感兴趣。这是一个相当难看的话题,你可以在这篇文章中了解更多。 MSDN Library article . 一旦您正确地处理了这一点,您的代码中也应该有一个很好的位置来处理这些任务。

        3
  •  -1
  •   Community Mohan Dere    9 年前

    我很想看到有人在这篇文章中提到这项技术: Typesafe fire-and-forget asynchronous delegate invocation in C#

    看起来,一个简单的扩展方法可以处理与任务交互的所有琐碎情况,并且能够调用Dispose。

    public static void FireAndForget<T>(this Action<T> act,T arg1)
    {
        var tsk = Task.Factory.StartNew( ()=> act(arg1),
                                         TaskCreationOptions.LongRunning);
        tsk.ContinueWith(cnt => cnt.Dispose());
    }