127

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

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

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

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

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

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

“在发布对任务的最后引用之前,请始终调用 Dispose。”

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

Task 类的 MSDN 页面对此没有评论,《Pro C#2010...》一书推荐了相同的模式,对任务处理也没有评论。

我知道如果我只是离开它,终结器最终会抓住它,但是当我做很多火和忘记这样的任务并且终结器线程不堪重负时,这会回来咬我吗?

所以我的问题是:

  • Dispose()在这种情况下不去上课是否可以接受Task?如果是这样,为什么以及是否存在风险/后果?
  • 有没有讨论这个的文档?
  • 或者是否有适当的方法来处理Task我错过的对象?
  • 还是有另一种使用 TPL 执行“开火即忘”任务的方法?
4

3 回答 3

114

MSDN 论坛中对此进行了讨论。

微软 pfx 团队的成员 Stephen Toub 这么说:

Task.Dispose 的存在是因为 Task 可能包装了在等待任务完成时使用的事件句柄,以防等待线程实际上必须阻塞(而不是旋转或可能执行它正在等待的任务)。如果您所做的只是使用延续,则永远不会分配该事件句柄
……
最好依靠终结来处理事情。

更新(2012 年 10 月)
Stephen Toub 发布了一篇题为“我需要处理任务吗? ”的博客。它提供了更多细节,并解释了 .Net 4.5 中的改进。

总结:您不需要在Task99% 的时间内处理对象。

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

  1. 从 .Net 4.5 开始,aTask分配内部等待句柄(Task对象中唯一的非托管资源)的唯一时间是当您明确使用IAsyncResult.AsyncWaitHandle,Task
  2. Task对象本身没有终结器;句柄本身被包装在一个带有终结器的对象中,所以除非它被分配,否则没有终结器可以运行。
于 2010-09-17T09:54:32.763 回答
13

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

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

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

于 2010-09-17T13:02:09.533 回答
-1

我很想看到有人对这篇文章中展示的技术进行权衡: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());
}
于 2010-12-09T14:20:13.243 回答