在TPL Task对象上调用Dispose()是否可以接受?
我想触发一个任务在后台线程上运行。 我不想等任务完成。
在.net 3.5中我会这样做:
ThreadPool.QueueUserWorkItem(d => { DoSomething(); });
在.net 4中,TPL是build议的方式。 我见过的常见模式是:
Task.Factory.StartNew(() => { DoSomething(); });
但是, StartNew()
方法返回一个实现IDisposable
的Task
对象。 这似乎被推荐这种模式的人忽视了。 Task.Dispose()
方法的MSDN文档说:
“在释放您最后一次引用任务之前,请始终调用Dispose。”
在完成任务之前,您不能调用任何处理任务,因此让主线程等待并调用处理器将首先打败后台线程。 也似乎没有任何完成/完成的事件可以用于清理。
Task类中的MSDN页面不对此进行评论,而“Pro C#2010 …”这本书推荐了相同的模式,并没有对任务处理进行评论。
我知道如果我只是离开它,终结者将抓住它,但是当我正在做这样的大火和忘记任务,并且终结者线程被淹没时,这是否会回来咬我?
所以我的问题是:
- 在这种情况下,不要在
Task
类上调用Dispose()
是否可以接受? 如果是这样,为什么还有风险/后果? - 有没有任何文件讨论这个?
- 或者有没有适当的方式处理我错过的
Task
对象? - 还是有另一种方式与TPL做火和忘记任务?
在MSDN论坛上有关于这个的讨论 。
微软pfx团队成员Stephen Toub这样说:
Task.Dispose的存在是由于任务潜在地包装了等待任务完成时使用的事件句柄,在等待线程实际上必须阻塞的情况下(相对于旋转或潜在地执行正在等待的任务)。 如果你正在使用continuation,那么这个事件句柄将永远不会被分配
…
最好依靠定稿来处理事情。
更新(2012年10月)
Stephen Toub发布了一篇名为“我是否需要处理任务 ”的博客? 其中给出了更多的细节,并解释了.Net 4.5中的改进。
总结:您不需要在99%的时间内处理Task
对象。
处理对象有两个主要的原因:及时,确定性地释放非托pipe资源,避免运行对象的终结器的代价。 大部分时间都不适用于Task
:
- 从.Net 4.5开始,
Task
分配内部等待句柄(Task
对象中唯一的非托pipe资源)的唯一情况是当您明确使用Task
的IAsyncResult.AsyncWaitHandle
时, -
Task
对象本身没有终结器; 该句柄本身被包装在一个带有终结器的对象中,所以除非被分配,否则没有终结器可以运行。
这是与Thread类相同的问题。 它消耗5个操作系统句柄,但不实现IDisposable。 原始devise者的好决定,当然没有合理的方法来调用Dispose()方法。 你必须首先调用Join()。
Task类为此添加了一个句柄,一个内部手动重置事件。 哪个是最便宜的操作系统资源。 当然,它的Dispose()方法只能释放一个事件句柄,而不是Thread消耗的5个句柄。 是的,不要打扰 。
要小心你应该对任务的IsFaulted属性感兴趣。 这是一个相当丑陋的话题,你可以在这个MSDN库文章中阅读更多关于它的内容 。 一旦你正确地处理这个问题,你也应该在你的代码中有一个好位置来处理这些任务。
我很想看到有人在这篇文章中显示的技术: 在C#中types安全的即发即弃asynchronous委托调用
它看起来像一个简单的扩展方法将处理与任务交互的所有微不足道的情况,并能够调用它的处置。
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()); }