不对 TPL Task 对象调用 Dispose()是否可以接受?

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

在.net 3.5中,我会这样做:

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

在.net 4中,TPL 是建议的方式。我看到的建议的常见模式是:

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

但是,StartNew()方法返回一个实现 IDisposableTask对象 推荐这种模式的人似乎忽略了这一点。关于 Task.Dispose()方法的 MSDN 文档说:

“总是在释放对任务的最后引用之前调用 Dispose。”

在任务完成之前,不能对其进行释放调用,因此让主线程等待和调用释放将首先破坏在后台线程上进行释放的目的。似乎也没有任何已完成/已完成的事件可用于清理。

Task 类上的 MSDN 页面没有对此进行评论,而书籍“ Pro C # 2010...”推荐了同样的模式,并且没有对任务处理进行评论。

我知道,如果我只是离开它,终结器将赶上它的最后,但这会回来咬我,当我正在做很多火和忘记任务像这样,终结器线程得到超负荷?

所以我的问题是:

  • 在这种情况下,不对 Task类调用 Dispose()是否可以接受?如果是这样,为什么会有风险/后果?
  • 是否有任何文件讨论这一点?
  • 或者有没有合适的方法来处理我遗漏的 Task对象?
  • 或者还有其他的方式做火和忘记任务与第三方物流?
32649 次浏览

有一个关于这个 在 MSDN 论坛上的讨论。

微软 pfx 团队成员斯蒂芬•图博(Stephen Toub)有话要说:

由于任务存在处理 可能包装事件句柄 在等待任务时使用 完成,在事件中的等待 线程实际上必须阻塞(如 反对旋转或者潜在的 执行它正在等待的任务)。 如果你只是在吸毒 继续,则该事件句柄将 永远不会被分配
...
最好还是依靠定稿来处理事情。

最新情况(2012年10月)
Stephen Toub 发布了一个名为 我需要处理任务吗?的博客,其中提供了更多细节,并解释了.Net 4.5的改进。

总之: 99% 的情况下不需要释放 Task对象。

处置对象有两个主要原因: 以及时、确定的方式释放非托管资源,以及避免运行对象的终结器的成本。这两种情况在大多数情况下都不适用于 Task:

  1. 从现在开始。Net 4.5,Task分配内部等待句柄(Task对象中唯一的非托管资源)的唯一时间是显式使用 TaskIAsyncResult.AsyncWaitHandle时,以及
  2. Task对象本身没有终结器; 句柄本身包装在带有终结器的对象中,因此除非分配了它,否则不会运行终结器。

这与 Thread 类的问题类似。它使用5个操作系统句柄,但不实现 IDisposable。原始设计者的好决定,当然很少有合理的方法来调用 Dispose ()方法。您必须首先调用 Join ()。

Task 类为此添加了一个句柄,即内部手动重置事件。这是最便宜的操作系统资源。当然,其 Dispose ()方法只能释放该事件句柄,而不能释放 Thread 使用的5个句柄。不用麻烦了.

请注意,您应该对任务的 IsFault 属性感兴趣。这是一个相当丑陋的主题,你可以在这个 MSDN 库文章阅读更多。一旦您正确地处理了这个问题,您还应该在代码中有一个很好的位置来处理这些任务。

我很乐意看到有人对这篇文章中展示的技术进行评价: C # 中类型安全的启动和忘记异步委托调用

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

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