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

时间:2023-01-03 20:20:14

I am building a Windows Service with .NET 4.0.

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

I have various unhandled exceptions thrown in Tasks, but they do not terminate my process as the MSDN documentation states (Parallel Tasks - see Unobserved Task Exceptions).

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

"If you don't give a faulted task the opportunity to propagate its exceptions (for example, by calling the Wait method), the runtime will escalate the task's unobserved exceptions according to the current .NET exception policy when the task is garbage-collected."

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

It behaves like this even when I use the most simple invokation of a task:

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

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

The service keeps on running fine when that is called.

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

According to the docs, the finalizer of the Task will rethrow the exception once the Task is GC'd but this does not appear to happen. MSDN states repeatedly that normal ".NET exception policy" results in process termination.

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

Why doesn't this terminate my app? The only thing I can think is there is somehow a reference to the task held somewhere (is it the lambda??)

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

5 个解决方案

#1


10  

From Essential C# 4.0, page 715, the following might help you out:

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

The unhandled exception during the Task's execution will be suppressed until a call to one of the task completion members: Wait(), Result,Task.WaitAll(), or Task.WaitAny(). Each ot these members will throw any unhandled exceptions that occurred within the task's execution.

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

Quite a few ways of handling exceptions are available. Have a look at MSDN here.

有很多种处理异常的方法。在这里看一下MSDN。

In answer to your comment, another quote from the same book explains why some exceptions are not propagated:

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

Although relatively rare, one of the exceptions for the general rule (of bubbling up) happens to be on Task. [..] Any Task-based exceptions thrown from the finalization queue during application exit will go suppressed. The behavior is set this way because frequently the effor to handle such an exception it too complex [...]

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

To overcome this, one way to do this elegantly is to create an exception handler task and to use ContinueWith to follow up after your task runs. You can then use parentTask.IsFaulted and gracefully crash, even in the event the exception is thrown in the finalization queue during application exit.

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

Tip: use the the flag OnlyOnFaulted to have this task run only when an exception occurs.

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

#2


5  

.NET 4.5 made some changes as to how UnobservedExceptions are handled

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

While unobserved exceptions will still cause the UnobservedTaskException event to be raised (not doing so would be a breaking change), the process will not crash by default.

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

This behavior can be configured though, so you can revert back to .Net 4.0 behavior by enabling ThrowUnobservedTaskExceptions like so:

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

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

It's recommended library developers enable this when testing to ensure they don't have any UnobservedExceptions being thrown. Otherwise library consumers with this setting enabled might see their programs crashing.

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

#3


5  

As suggested by @Hans and @CodeInChaos I found the only way to rethrow the unhandled exception (thus killing the process) is to force the Finalizer to run (Note: Make sure you dont do this in the ContinueWith()!):

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

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

In my particular circumstances the Task was not garbage collected because the flow of the program depended on the Task being succesful. Without the flow continuing my app would not do anything to cause a GC (allocate objects etc).

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

What is interesting is that even doing a GC.Collect() is not enough. The Task finalizer still did not run. The GC.WaitForPendingFinalizers() had to be called explicitly. (I suspect I do not understand subtleties around Finalization).

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

To summarize: Dont expect a TPL Task's unobserved exception behaviour to be similar to other threading mechanisms unhandled exception behaviour (e.g. QueueUserWorkItem). In most practical situations you need to explicity check for Exceptions from Tasks: you cannot rely on unobserved exceptions being brought to your attention in the way they would with a QUWI or similar because you will only see them thrown from the Finalizer which is totally unpredictable.

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

Edit: See my other answer concerning .NET 4.5

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

#4


0  

You may create it with TaskCreationOptions.AttachedToParent. According to Nested Tasks and Child Tasks (MSDN), Exceptions are then propagated to your thread. I do not know, however, whether this is elegant or not.

您可以使用TaskCreationOptions.AttachedToParent创建它。根据嵌套任务和子任务(MSDN),异常将传播到您的线程。然而,我不知道这是否优雅。

Microsoft does not recommend this "in most cases". Somebody else might know in what case this may be sensible. From the same article:

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

You can use attached child tasks to create tightly-synchronized graphs of asynchronous operations. However, in most scenarios, we recommend that you use nested tasks because the relationships with other tasks are less complex. That is why tasks created inside other tasks are nested by default, and you must explicitly specify the AttachedToParent option to create a child task.

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

Cheers, Matthias

#5


-1  

According to this nice blog post if you want to crash your app as soon as you have unhandled exception in your task then you can continue your task something like below:

根据这篇不错的博客文章,如果您想在任务中出现未处理的异常时立即崩溃您的应用程序,那么您可以继续执行以下任务:

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

#1


10  

From Essential C# 4.0, page 715, the following might help you out:

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

The unhandled exception during the Task's execution will be suppressed until a call to one of the task completion members: Wait(), Result,Task.WaitAll(), or Task.WaitAny(). Each ot these members will throw any unhandled exceptions that occurred within the task's execution.

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

Quite a few ways of handling exceptions are available. Have a look at MSDN here.

有很多种处理异常的方法。在这里看一下MSDN。

In answer to your comment, another quote from the same book explains why some exceptions are not propagated:

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

Although relatively rare, one of the exceptions for the general rule (of bubbling up) happens to be on Task. [..] Any Task-based exceptions thrown from the finalization queue during application exit will go suppressed. The behavior is set this way because frequently the effor to handle such an exception it too complex [...]

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

To overcome this, one way to do this elegantly is to create an exception handler task and to use ContinueWith to follow up after your task runs. You can then use parentTask.IsFaulted and gracefully crash, even in the event the exception is thrown in the finalization queue during application exit.

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

Tip: use the the flag OnlyOnFaulted to have this task run only when an exception occurs.

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

#2


5  

.NET 4.5 made some changes as to how UnobservedExceptions are handled

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

While unobserved exceptions will still cause the UnobservedTaskException event to be raised (not doing so would be a breaking change), the process will not crash by default.

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

This behavior can be configured though, so you can revert back to .Net 4.0 behavior by enabling ThrowUnobservedTaskExceptions like so:

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

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

It's recommended library developers enable this when testing to ensure they don't have any UnobservedExceptions being thrown. Otherwise library consumers with this setting enabled might see their programs crashing.

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

#3


5  

As suggested by @Hans and @CodeInChaos I found the only way to rethrow the unhandled exception (thus killing the process) is to force the Finalizer to run (Note: Make sure you dont do this in the ContinueWith()!):

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

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

In my particular circumstances the Task was not garbage collected because the flow of the program depended on the Task being succesful. Without the flow continuing my app would not do anything to cause a GC (allocate objects etc).

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

What is interesting is that even doing a GC.Collect() is not enough. The Task finalizer still did not run. The GC.WaitForPendingFinalizers() had to be called explicitly. (I suspect I do not understand subtleties around Finalization).

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

To summarize: Dont expect a TPL Task's unobserved exception behaviour to be similar to other threading mechanisms unhandled exception behaviour (e.g. QueueUserWorkItem). In most practical situations you need to explicity check for Exceptions from Tasks: you cannot rely on unobserved exceptions being brought to your attention in the way they would with a QUWI or similar because you will only see them thrown from the Finalizer which is totally unpredictable.

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

Edit: See my other answer concerning .NET 4.5

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

#4


0  

You may create it with TaskCreationOptions.AttachedToParent. According to Nested Tasks and Child Tasks (MSDN), Exceptions are then propagated to your thread. I do not know, however, whether this is elegant or not.

您可以使用TaskCreationOptions.AttachedToParent创建它。根据嵌套任务和子任务(MSDN),异常将传播到您的线程。然而,我不知道这是否优雅。

Microsoft does not recommend this "in most cases". Somebody else might know in what case this may be sensible. From the same article:

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

You can use attached child tasks to create tightly-synchronized graphs of asynchronous operations. However, in most scenarios, we recommend that you use nested tasks because the relationships with other tasks are less complex. That is why tasks created inside other tasks are nested by default, and you must explicitly specify the AttachedToParent option to create a child task.

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

Cheers, Matthias

#5


-1  

According to this nice blog post if you want to crash your app as soon as you have unhandled exception in your task then you can continue your task something like below:

根据这篇不错的博客文章,如果您想在任务中出现未处理的异常时立即崩溃您的应用程序,那么您可以继续执行以下任务:

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