在 TPL 中,如果任务抛出异常,该异常被捕获并存储在 Task.Exception,然后遵循 观察到的异常。如果从未观察到它,它最终会在终结器线程上重新抛出并使进程崩溃。
有没有办法防止任务捕获该异常,而只是让它传播?
我感兴趣的任务已经在 UI 线程上运行(由 TaskScheduler.FromCurrentSynchronizationContext),并且我希望异常转义,以便可以由我现有的 Application.ThreadException 处理程序。
我基本上希望任务中未处理的异常表现得像按钮单击处理程序中未处理的异常:立即在 UI 线程上传播,并由 ThreadException 处理。
In TPL, if an exception is thrown by a Task, that exception is captured and stored in Task.Exception, and then follows all the rules on observed exceptions. If it's never observed, it's eventually rethrown on the finalizer thread and crashes the process.
Is there a way to prevent the Task from catching that exception, and just letting it propagate instead?
The Task I'm interested in would already be running on the UI thread (courtesy of TaskScheduler.FromCurrentSynchronizationContext), and I want the exception to escape so it can be handled by my existing Application.ThreadException handler.
I basically want unhandled exceptions in the Task to behave like unhandled exceptions in a button-click handler: immediately propagate on the UI thread, and be handled by ThreadException.
发布评论
评论(4)
好吧,乔...正如所承诺的,以下是如何使用自定义
TaskScheduler
子类来一般性地解决此问题。我已经测试了这个实现,它的效果非常好。 不要忘记如果您想看到Application.ThreadException
实际触发,则不能附加调试器!自定义TaskScheduler
这个自定义TaskScheduler 实现在“诞生”时与特定的SynchronizationContext 绑定在一起,并将接受它需要执行的每个传入的Task,将Continuation 链接到它上面仅当逻辑
Task
发生故障时才会触发,并且当逻辑Task
发生故障时,它会Post
返回到 SynchronizationContext,并在其中抛出来自Task
的异常> 那个错了。关于此实现的一些注释/免责声明:
Task
排队。我将其作为读者的练习。这并不难,只是......没有必要演示您所要求的功能。好的,现在您有几个使用此 TaskScheduler 的选项:
预配置 TaskFactory 实例
这种方法允许您设置一次
TaskFactory
,然后使用该工厂实例启动的任何任务都将使用自定义TaskScheduler
。这基本上看起来像这样:在应用程序启动时
整个代码
显式 TaskScheduler 每次调用
另一种方法是仅创建自定义
TaskScheduler
的实例,然后将其传递到StartNew
每次启动任务时都会在默认的TaskFactory
上。应用程序启动时
整个代码
Ok Joe... as promised, here's how you can generically solve this problem with a custom
TaskScheduler
subclass. I've tested this implementation and it works like a charm. Don't forget you can't have the debugger attached if you want to seeApplication.ThreadException
to actually fire!!!The Custom TaskScheduler
This custom TaskScheduler implementation gets tied to a specific
SynchronizationContext
at "birth" and will take each incomingTask
that it needs to execute, chain a Continuation on to it that will only fire if the logicalTask
faults and, when that fires, itPost
s back to the SynchronizationContext where it will throw the exception from theTask
that faulted.Some notes/disclaimers on this implementation:
Task
to be worked on. I leave this as an excercise for the reader. It's not hard, just... not necessary to demonstrate the functionality you're asking for.Ok, now you have a couple options for using this TaskScheduler:
Pre-configure TaskFactory Instance
This approach allows you to setup a
TaskFactory
once and then any task you start with that factory instance will use the customTaskScheduler
. That would basically look something like this:At application startup
Throughout code
Explicit TaskScheduler Per-Call
Another approach is to just create an instance of the custom
TaskScheduler
and then pass that intoStartNew
on the defaultTaskFactory
every time you start a task.At application startup
Throughout code
我找到了一个在某些时候有效的解决方案。
单个任务
这会在 UI 线程上安排对
task.Wait()
的调用。由于我不会等待直到我知道任务已经完成,所以它实际上不会阻塞;它只会检查是否有异常,如果有,就会抛出异常。由于SynchronizationContext.Post
回调是直接从消息循环执行的(在Task
的上下文之外),因此 TPL 不会停止异常,并且它可以正常传播-- 就像按钮单击处理程序中未处理的异常一样。一个额外的问题是,如果任务被取消,我不想调用
WaitAll
。如果您等待已取消的任务,TPL 会抛出TaskCanceledException
,重新抛出该异常是没有意义的。多个任务
在我的实际代码中,我有多个任务——一个初始任务和多个延续任务。如果其中任何一个(可能不止一个)出现异常,我想将 AggregateException 传播回 UI 线程。处理方法如下:
同一个故事:所有任务完成后,在
Task
上下文之外调用WaitAll
。它不会阻塞,因为任务已经完成;如果任何任务出现故障,这只是抛出 AggregateException 的一种简单方法。起初我担心,如果其中一个延续任务使用类似
TaskContinuationOptions.OnlyOnRanToCompletion
,并且第一个任务发生故障,则WaitAll
调用可能会挂起(因为延续任务永远不会运行,我担心WaitAll
会阻塞等待它运行)。但事实证明,TPL 设计者比这更聪明 - 如果由于OnlyOn
或NotOn
标志而导致继续任务不会运行,则该继续任务将转换为 < code>Canceled 状态,因此不会阻塞WaitAll
。编辑
当我使用多任务版本时,
WaitAll
调用会抛出AggregateException
,但该AggregateException
不会到达 < code>ThreadException 处理程序:仅将其内部异常的一个传递给ThreadException
。因此,如果多个任务引发异常,则只有其中一个任务到达线程异常处理程序。我不清楚为什么会这样,但我正在努力弄清楚。I found a solution that works adequately some of the time.
Single task
This schedules a call to
task.Wait()
on the UI thread. Since I don't do theWait
until I know the task is already done, it won't actually block; it will just check to see if there was an exception, and if so, it will throw. Since theSynchronizationContext.Post
callback is executed straight from the message loop (outside the context of aTask
), the TPL won't stop the exception, and it can propagate normally -- just as if it was an unhandled exception in a button-click handler.One extra wrinkle is that I don't want to call
WaitAll
if the task was canceled. If you wait on a canceled task, TPL throws aTaskCanceledException
, which it makes no sense to re-throw.Multiple tasks
In my actual code, I have multiple tasks -- an initial task and multiple continuations. If any of those (potentially more than one) get an exception, I want to propagate an
AggregateException
back to the UI thread. Here's how to handle that:Same story: once all the tasks have completed, call
WaitAll
outside the context of aTask
. It won't block, since the tasks are already completed; it's just an easy way to throw anAggregateException
if any of the tasks faulted.At first I worried that, if one of the continuation tasks used something like
TaskContinuationOptions.OnlyOnRanToCompletion
, and the first task faulted, then theWaitAll
call might hang (since the continuation task would never run, and I worried thatWaitAll
would block waiting for it to run). But it turns out the TPL designers were cleverer than that -- if the continuation task won't be run because ofOnlyOn
orNotOn
flags, that continuation task transitions to theCanceled
state, so it won't block theWaitAll
.Edit
When I use the multiple-tasks version, the
WaitAll
call throws anAggregateException
, but thatAggregateException
doesn't make it through to theThreadException
handler: instead only one of its inner exceptions gets passed toThreadException
. So if multiple tasks threw exceptions, only one of them reaches the thread-exception handler. I'm not clear on why this is, but I'm trying to figure it out.我不知道如何让这些异常像主线程中的异常一样传播。为什么不将与
Application.ThreadException
挂钩的同一处理程序挂钩到TaskScheduler.UnobservedTaskException
也是如此?There's no way that I'm aware of to have these exceptions propagate up like exceptions from the main thread. Why not just hook the same handler that you're hooking to
Application.ThreadException
toTaskScheduler.UnobservedTaskException
as well?有类似这套衣服的吗?
Does something like this suit?