Task.Run和Task.Factory.StartNew之间的exception处理不同

我在使用Task.Factory.StartNew并尝试捕获抛出的exception时遇到了一个问题。 在我的应用程序中,我有一个长期运行的任务,我想封装在Task.Factory.StartNew(.., TaskCreationOptions.LongRunning);

但是,当我使用Task.Factory.StartNew时,不会捕获exception。 然而,当我使用Task.Run ,它正如我所期望的Task.Run ,我认为它只是Task.Factory.StartNew一个包装器(根据例如这篇MSDN文章 )。

这里提供了一个工作示例,区别在于在使用Task.Run时将exception写入控制台,但在使用Factory.StartNew时则不会。

我的问题是:
如果我有一个可以抛出exception的LongRunning任务,我应该如何在调用代码中处理它们?

 private static void Main(string[] args) { Task t = RunLongTask(); t.Wait(); Console.WriteLine(t.Result); Console.ReadKey(); } private async static Task RunLongTask() { try { await RunTaskAsync(); } catch (Exception e) { Console.WriteLine(e); return false; } Console.WriteLine("success"); return true; } private static Task RunTaskAsync() { //return Task.Run(async () => // { // throw new Exception("my exception"); // }); return Task.Factory.StartNew( async () => { throw new Exception("my exception"); }); } 

您的问题是StartNew不像Task.Run使用async委托。 StartNew的返回类型是Task (可以转换为Task )。 “外部” Task表示方法的开头,“内部” Task表示方法的完成(包括任何例外)。

要进入内部Task ,您可以使用Unwrap 。 或者您可以使用Task.Run而不是StartNew来获取async代码。 LongRunning只是一个优化提示,实际上是可选的。 Stephen Toub有一篇关于StartNewRun之间差异的博文,以及为什么Run (通常)更适合async代码。

从下面的@usr注释更新: LongRunning仅适用于async方法的开头(直到第一个未完成的操作await编辑)。 所以在这种情况下使用Task.Run几乎肯定更好。

我会将一些评论写入答案中,因为它们对我们有所帮助:

LongRunning与强制在实践中创建新线程完全相同。 并且你的异步方法可能很长时间都不在该线程上(它在第一个等待点处被取消)。 在这种情况下,您不希望LongRunning。

async方法运行多长时间并不重要。 线程在第一次等待时被销毁(对未完成的任务进行操作)。

编译器可以以任何方式使用此提示吗? 编译器通常无法以任何主要方式分析您的代码。 此外,编译器对TPL一无所知。 TPL是一个图书馆。 而这个库将始终启动一个新的线程。 指定LongRunning如果您的任务几乎总是将100%CPU刻录多秒,或者以非常高的概率阻止多秒。

我的猜测是你不想在这里使用LongRunning ,因为如果你阻塞了,为什么你首先使用异步? async是关于不阻止但是脱离线程。

首次打开任务时应该可以:

 await RunTaskAsync().Unwrap(); 

或者:

 await await RunTaskAsync();