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

Sim*_*ens 117 .net c# multithreading dispose task-parallel-library

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

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

ThreadPool.QueueUserWorkItem(d => { DoSomething(); });
Run Code Online (Sandbox Code Playgroud)

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

Task.Factory.StartNew(() => { DoSomething(); });
Run Code Online (Sandbox Code Playgroud)

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

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

你不能在任务完成之前调用dispose,所以让主线程等待并调用dispose会首先打破后台线程上的操作.似乎也没有任何已完成/已完成的事件可用于清理.

Task类上的MSDN页面没有对此进行评论,并且"Pro C#2010 ..."一书推荐了相同的模式,并且没有对任务处理做出评论.

我知道如果我离开它,终结者最终会抓住它,但当我做了大量的火灾并且忘记了这样的任务并且终结者线程被淹没时,它会回来咬我吗?

所以我的问题是:

  • 是否可以接受不叫Dispose()Task在这种情况下类?如果是这样,为什么会有风险/后果?
  • 有没有讨论这个的文件?
  • 或者是否有适当的方法来处理Task我错过的物体?
  • 或者是否有其他方法可以使用TPL执行解雇和忘记任务?

Kir*_*kov 102

在MSDN论坛中有关于此的讨论.

微软pfx团队成员Stephen Toub对此说:

Task.Dispose存在是由于Task可能包装在等待任务完成时使用的事件句柄,如果等待线程实际上必须阻塞(而不是旋转或可能正在执行它正在等待的任务).如果您所做的只是使用continuation,那么永远不会分配该事件句柄
......
最好依靠finalization来处理事情.

更新(2012年10月)
Stephen Toub发布了一个名为"我是否需要处理任务? "的博客它提供了更多细节,并解释了.Net 4.5的改进.

总结:您不需要Task99%的时间处置对象.

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

  1. 作为.NET 4.5中,唯一一次Task分配的内部等待句柄(中唯一的非托管资源Task对象)时,明确使用IAsyncResult.AsyncWaitHandleTask,并
  2. Task对象本身没有终结; 句柄本身用一个终结器包裹在一个对象中,所以除非它被分配,否则没有终结器可以运行.

  • 谢谢,有趣.它违背了MSDN文档.MS或.net团队是否有任何官方消息称这是可接受的代码.在讨论结束时还提出了"如果实施在未来版本中发生变化会怎么样" (3认同)
  • @Simon:(1)您引用的MSDN文档是通用建议,具体案例有更具体的建议(例如,当使用`BeginInvoke`在UI线程上运行代码时,不需要在WinForms中使用`EndInvoke`).(2)作为有效使用PFX的常规发言人,Stephen Toub非常清楚(例如在http://channel9.msdn.com/上),所以如果有人能提供良好的指导,那么他就是这样.注意他的第二段:有时候把事情留给终结者会更好. (2认同)

Han*_*ant 14

这与Thread类相同.它消耗5个操作系统句柄但不实现IDisposable.原始设计师的好决定,当然有几种合理的方法来调用Dispose()方法.你必须先调用Join().

Task类为此添加一个句柄,即内部手动重置事件.哪个是最便宜的操作系统资源.当然,它的Dispose()方法只能释放一个事件句柄,而不是Thread消耗的5个句柄. 是的,不要打扰.

请注意,您应该对任务的IsFaulted属性感兴趣.这是一个相当丑陋的话题,你可以在这篇MSDN Library文章中阅读更多相关内容.一旦你正确处理了这个问题,你也应该在你的代码中有一个很好的位置来处理这些任务.

  • 但是在大多数情况下,任务不会创建`Thread`,它使用ThreadPool. (6认同)