当Task有未处理的异常时,为什么我的进程不会终止?

Jac*_*eja 15 .net .net-4.0 task-parallel-library

我正在使用.NET 4.0构建Windows服务.

我在任务中抛出了各种未处理的异常,但它们不会像MSDN文档所述那样终止我的进程(并行任务 - 请参阅未观察到的任务异常).

"如果您没有给故障任务提供传播其异常的机会(例如,通过调用Wait方法),那么当任务被垃圾收集时,运行时将根据当前的.NET异常策略升级任务的未观察异常. ".

即使我使用最简单的任务调用,它的行为也是如此:

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

调用该服务时,该服务保持正常运行.

根据文档,任务的终结器将在任务为GC后重新抛出异常,但这似乎不会发生.MSDN反复声明,正常的".NET异常策略"会导致进程终止.

为什么不终止我的应用程序?我能想到的唯一一件事就是以某种方式提到某个地方的任务(是lambda ??)

Abe*_*bel 10

Essential C#4.0,第715页,以下内容可能会帮助您:

任务的执行过程中未处理的异常将被抑制直至任务完成成员之一的呼叫:Wait(), Result,Task.WaitAll()Task.WaitAny().这些成员中的每一个都将抛出任务执行中发生的任何未处理的异常.

可以使用多种处理异常的方法.有一个看MSDN这里.

在回答你的评论时,同一本书的另一个引用解释了为什么没有传播一些例外:

虽然相对罕见,但一般规则(冒泡)的例外之一恰好在任务上.[..]在应用程序退出期间从终结队列抛出的任何基于任务的异常都将被禁止.行为是这样设置的,因为经常处理这样一个异常的effor太复杂了[...]

为了克服这个问题,优雅地执行此操作的一种方法是创建异常处理程序任务,并ContinueWith在任务运行后用于跟进.然后parentTask.IsFaulted,即使在应用程序退出期间在终结队列中抛出异常的情况下,您也可以使用并正常崩溃.

提示:使用该标志OnlyOnFaulted仅在发生异常时才运行此任务.

  • 是的,我知道.但我不打算等待任务.根据文档,它应该抛出一次最终确定 (2认同)

Jac*_*eja 6

.NET 4.5对如何处理UnobservedExceptions进行了一些更改

虽然未观察到的异常仍会导致引发UnobservedTaskException事件(不会这样做会发生重大变化),默认情况下进程不会崩溃.

但是可以配置此行为,因此您可以通过如下启用恢复为.Net 4.0行为ThrowUnobservedTaskExceptions:

<configuration> 
    <runtime> 
        <ThrowUnobservedTaskExceptions enabled="true"/> 
    </runtime>
</configuration>
Run Code Online (Sandbox Code Playgroud)

建议库开发人员在测试时启用此功能,以确保它们不会抛出任何UnobservedExceptions.否则启用此设置的库消费者可能会看到他们的程序崩溃.


Jac*_*eja 5

正如@Hans和@CodeInChaos所建议的那样,我发现重新抛出未处理的异常(从而杀死进程)的唯一方法是强制终结器运行(注意:确保你不要在ContinueWith()!中执行此操作):

GC.Collect(); 
GC.WaitForPendingFinalizers();
Run Code Online (Sandbox Code Playgroud)

在我的特殊情况下,任务不是垃圾收集,因为程序的流程取决于任务是否成功.如果没有流程继续我的应用程序将不会做任何事情导致GC(分配对象等).

有趣的是,即使做一个GC.Collect()还不够.任务终结器仍然没有运行.在GC.WaitForPendingFinalizers()必须显式调用.(我怀疑我不理解Finalization的细微之处).

总结一下:不要指望TPL任务的未观察到的异常行为类似于其他线程机制未处理的异常行为(例如QueueUserWorkItem).在大多数实际情况中,您需要明确检查"任务中的异常":您不能依赖于使用QUWI或类似方式引起注意的未观察到的异常,因为您只会看到它们从Finalizer中抛出,这是完全不可预测的.

编辑:请参阅我关于.NET 4.5的其他答案

  • @CodeInChaos.您是否了解.NET 2.0对异常策略所做的彻底更改?他们使所有未处理的异常导致进程崩溃,正是因为人们不知道发生了异常.换句话说,他们是"未被观察到的".为什么TPL不遵循让用户知道发生异常的框架的相同原则?当然,未观察到的异常表明存在错误.我想知道错误(默认情况下),而不是隐藏它们. (2认同)