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

时间:2011-08-08 12:42:24

标签: .net .net-4.0 task-parallel-library

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

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

  

“如果你没有给出有故障的任务,那就有机会传播它   异常(例如,通过调用Wait方法),运行时将   根据当前情况升级任务的未观察到的异常   任务被垃圾收集时的.NET异常策略。“

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

Task.Factory.StartNew(() => { throw new Exception(); } 

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

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

为什么不终止我的应用?我能想到的唯一的事情是某种方式对某个地方的任务的引用(是lambda ??)

5 个答案:

答案 0 :(得分:10)

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

  

将禁止执行任务期间的未处理异常   直到调用其中一个任务完成成员:Wait(),   Result,Task.WaitAll()Task.WaitAny()。这些成员中的每一个都会   抛出任务中发生的任何未处理的异常   执行。

可以使用多种处理异常的方法。有a look at MSDN here

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

  

虽然比较少见,但一般规则的例外情况之一   (起泡)碰巧在任务上。 [..]任何基于任务的例外   在应用程序退出期间从终结队列中抛出将进行   抑制。行为是这样设置的,因为经常是effor   处理这样的例外太复杂了[...]

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

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

答案 1 :(得分:6)

.NET 4.5对how UnobservedExceptions are handled

进行了一些更改
  

虽然未观察到的异常仍将导致   要引发的UnobservedTaskException事件(不这样做会是一个   破坏更改),默认情况下进程不会崩溃。

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

<configuration> 
    <runtime> 
        <ThrowUnobservedTaskExceptions enabled="true"/> 
    </runtime>
</configuration>

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

答案 2 :(得分:5)

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

GC.Collect(); 
GC.WaitForPendingFinalizers();

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

有趣的是,即使进行GC.Collect()也是不够的。任务终结器仍然没有运行。必须明确调用GC.WaitForPendingFinalizers()。 (我怀疑我不理解Finalization的细微之处)。

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

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

答案 3 :(得分:0)

您可以使用TaskCreationOptions.AttachedToParent创建它。根据{{​​3}},异常会传播到您的主题。但是,我不知道这是否优雅。

在大多数情况下,Microsoft不建议这样做。其他人可能知道在什么情况下这可能是明智的。来自同一篇文章:

  

您可以使用附加的子任务来创建紧密同步   异步操作的图表。但是,在大多数情况下,我们   建议您使用嵌套任务因为与之关系   其他任务不太复杂。这就是在其他内部创建任务的原因   默认情况下嵌套任务,您必须明确指定   AttachedToParent选项用于创建子任务。

干杯,马蒂亚斯

答案 4 :(得分:0)

根据这个不错的blog post,如果您想在任务中出现未处理的异常时立即崩溃您的应用,那么您可以继续执行以下任务:

public static Task FailFastOnException(this Task task) 
{ 
    task.ContinueWith(c => Environment.FailFast(“Task faulted”, c.Exception), 
    TaskContinuationOptions.OnlyOnFaulted | 
    TaskContinuationOptions.ExecuteSynchronously | 
    TaskContinuationOptions.DetachedFromParent); 
    return task; 
}