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

C# 对TPL任务对象不调用Dispose()是否可以接受?,c#,.net,multithreading,dispose,task-parallel-library,C#,.net,Multithreading,Dispose,Task Parallel Library,我想触发一个在后台线程上运行的任务。我不想等待任务完成 在.net 3.5中,我会这样做: ThreadPool.QueueUserWorkItem(d => { DoSomething(); }); 在.NET4中,建议使用TPL。我看到的常见模式是: Task.Factory.StartNew(() => { DoSomething(); }); 但是,该方法返回的对象实现了IDisposable。这 似乎被推荐这种模式的人忽略了。有关该方法的MSDN文档说明: “在释放对任

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

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

ThreadPool.QueueUserWorkItem(d => { DoSomething(); });
在.NET4中,建议使用TPL。我看到的常见模式是:

Task.Factory.StartNew(() => { DoSomething(); });
但是,该方法返回的对象实现了
IDisposable
。这 似乎被推荐这种模式的人忽略了。有关该方法的MSDN文档说明:

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

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

Task类上的MSDN页面没有对此进行评论,而《Pro C#2010…》一书也推荐了相同的模式,并且没有对任务处理进行评论

我知道如果我离开它,终结器最终会抓住它,但是当我做很多火&忘记这样的任务,终结器线程被淹没时,它会回来咬我吗

因此,我的问题是:

  • 在这种情况下,不调用
    Task
    类上的
    Dispose()
    是否可以接受?如果是,为什么会有风险/后果
  • 是否有任何文件对此进行了讨论
  • 或者有没有合适的方法来处理我错过的
    任务
    对象
  • 或者,有没有其他方法可以通过TPL完成fire&forget任务

    • 关于这一点有一个讨论

      微软pfx团队成员Stephen Toub说:

      任务。由于任务原因,存在Dispose 潜在地包装事件句柄 在等待任务完成时使用 在等待的情况下完成 线程实际上必须阻塞(如 反对旋转或潜在的 执行它正在等待的任务)。 如果你所做的只是使用 continuations,该事件句柄将 永远不要被分配

      可能最好依靠最终定稿来处理事情

      更新(2012年10月)
      Stephen Toub发布了一个名为的博客,提供了更多细节,并解释了.NET4.5的改进

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

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

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

    • 这与Thread类的问题类型相同。它使用5个操作系统句柄,但不实现IDisposable。对于最初的设计人员来说,这是一个很好的决定,当然调用Dispose()方法的合理方法很少。您必须首先调用Join()

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


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

      我很想看到有人对这篇文章中显示的技术进行权衡:

      看起来一个简单的扩展方法将处理与任务交互的所有琐碎情况,并能够对其调用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());
      }
      
      公共静态无效FireAndForget(本行动法案,T arg1)
      {
      var tsk=Task.Factory.StartNew(()=>act(arg1),
      TaskCreationOptions.LongRunning);
      tsk.ContinueWith(cnt=>cnt.Dispose());
      }
      
      谢谢,很有趣。不过,这与MSDN文档背道而驰。微软或.net团队是否有任何官方说法表明这是可以接受的代码。在讨论结束时还提出了一点,“如果未来版本中的实现发生变化怎么办”事实上,我刚刚注意到该线程中的回答者确实在微软工作,似乎在pfx团队中,所以我想这是某种形式的官方回答。但有人建议,在所有情况下,这种做法都是行不通的。如果存在潜在泄漏,我是否最好恢复到我知道是安全的ThreadPool.QueueUserWorkItem?是的,非常奇怪的是,有一个您可能不会调用的Dispose。如果你看一下这里和这里的样本,它们不是在处理任务。然而,MSDN中的代码示例有时会演示非常糟糕的技术。这个问题的答案同样适用于Microsoft。@Simon:(1)您引用的MSDN文档是通用的建议,特定情况下有更具体的建议(例如,当使用
      BeginInvoke
      在UI线程上运行代码时,不需要在WinForms中使用
      EndInvoke
      )。(2) 斯蒂芬·图布(Stephen Toub)作为有效使用PFX(例如on)的常客而广为人知,因此,如果有人能提供良好的指导,那么他就是。注意他的第二段:有时把事情留给最终定稿人会更好。相关:(参见)当然,这无法处理由
      ContinueWith
      返回的
      任务
      实例,但参见Stephen Toub的引用是公认的答案:如果没有任何东西执行阻塞,则没有任何东西可处理