问题描述
在TPL,如果有异常被抛出,该异常被捕获并存储在,然后跟随所有在的规则。如果它从未观察到,它最终会重新引发终结线程和崩溃的过程。
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?
任务我感兴趣的是将已经在UI线程上运行的),我想除了逃跑,因此它可以通过我现有的处理程序。
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.
我基本上要在任务未处理的异常表现得像一个按钮单击处理程序未处理的异常:立即在UI线程上传播,并通过ThreadException处理
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.
推荐答案
好吧......乔如许,这里是如何你可以用一般的自定义的TaskScheduler
子类解决这个问题。我测试过这个实现,它的工作原理就像一个魅力。 如果你想看到 Application.ThreadException
实际上火不要忘记您已经不能调试器附着!
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 see Application.ThreadException
to actually fire!!!
这定制的TaskScheduler执行被绑定到特定的的SynchronizationContext
在诞生,并会采取每个传入工作
,它需要执行,链延续到这只会火如果逻辑任务
故障和,当火灾,它发表
发回给的SynchronizationContext它会抛出从任务异常
该故障
This custom TaskScheduler implementation gets tied to a specific SynchronizationContext
at "birth" and will take each incoming Task
that it needs to execute, chain a Continuation on to it that will only fire if the logical Task
faults and, when that fires, it Post
s back to the SynchronizationContext where it will throw the exception from the Task
that faulted.
public sealed class SynchronizationContextFaultPropagatingTaskScheduler : TaskScheduler
{
#region Fields
private SynchronizationContext synchronizationContext;
private ConcurrentQueue<Task> taskQueue = new ConcurrentQueue<Task>();
#endregion
#region Constructors
public SynchronizationContextFaultPropagatingTaskScheduler() : this(SynchronizationContext.Current)
{
}
public SynchronizationContextFaultPropagatingTaskScheduler(SynchronizationContext synchronizationContext)
{
this.synchronizationContext = synchronizationContext;
}
#endregion
#region Base class overrides
protected override void QueueTask(Task task)
{
// Add a continuation to the task that will only execute if faulted and then post the exception back to the synchronization context
task.ContinueWith(antecedent =>
{
this.synchronizationContext.Post(sendState =>
{
throw (Exception)sendState;
},
antecedent.Exception);
},
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
// Enqueue this task
this.taskQueue.Enqueue(task);
// Make sure we're processing all queued tasks
this.EnsureTasksAreBeingExecuted();
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// Excercise for the reader
return false;
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return this.taskQueue.ToArray();
}
#endregion
#region Helper methods
private void EnsureTasksAreBeingExecuted()
{
// Check if there's actually any tasks left at this point as it may have already been picked up by a previously executing thread pool thread (avoids queueing something up to the thread pool that will do nothing)
if(this.taskQueue.Count > 0)
{
ThreadPool.UnsafeQueueUserWorkItem(_ =>
{
Task nextTask;
// This thread pool thread will be used to drain the queue for as long as there are tasks in it
while(this.taskQueue.TryDequeue(out nextTask))
{
base.TryExecuteTask(nextTask);
}
},
null);
}
}
#endregion
}
的一些注意事项/在此实现免责声明:
- 如果您使用无参数的构造函数,它会拿起当前的SynchronizationContext ...所以,如果你只是建立在这样一个WinForms线程(主要形式的构造函数,等等),它会自动工作。奖金,我也有一个构造函数,你可以明确地传递,你从别的地方拿到的SynchronizationContext。
- 我没有那么此实现将只是永远只是排队所提供的TryExecuteTaskInline的实现
工作
来进行工作的。我离开这个是为读者提供一个锻炼; Tibial。这并不难,只是...没必要证明你问了。 - 我用一个简单的/原始的方法来调度/执行,利用线程池的任务功能。肯定有更丰富的实现是有,但同样这种实现的焦点仅仅是关于编组例外回应用程序线程
- If you use the parameterless constructor, it will pick up the current SynchronizationContext... so if you just construct this on a WinForms thread (main form constructor, whatever) and it will work automatically. Bonus, I also have a constructor where you can explicitly pass in the SynchronizationContext that you got from somewhere else.
- I have not provided an implementation of TryExecuteTaskInline so this implementation will just always just queue the
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. - I'm using a simple/primitive approach to scheduling/executing the Tasks that leverages the ThreadPool. There are definitely richer implementations to be had, but again the focus of this implementation is simply about marshaling exceptions back to the "Application" thread
好了,现在你有使用这个的TaskScheduler几个选项:
Ok, now you have a couple options for using this TaskScheduler:
这方法允许你设置一个 TaskFactory
一次,然后你开始与该工厂实例将使用自定义的任何任务的TaskScheduler
。这将基本上是这个样子:
This approach allows you to setup a TaskFactory
once and then any task you start with that factory instance will use the custom TaskScheduler
. That would basically look something like this:
private static readonly TaskFactory MyTaskFactory = new TaskFactory(new SynchronizationContextFaultPropagatingTaskScheduler());
在整个代码
Throughout code
MyTaskFactory.StartNew(_ =>
{
// ... task impl here ...
});
明确的TaskScheduler每呼叫
另一个方法是只需创建自定义的的TaskScheduler
的一个实例,然后传递到 StartNew
上的默认 TaskFactory
每次启动任务的时间。
Explicit TaskScheduler Per-Call
Another approach is to just create an instance of the custom TaskScheduler
and then pass that into StartNew
on the default TaskFactory
every time you start a task.
private static readonly SynchronizationContextFaultPropagatingTaskScheduler MyFaultPropagatingTaskScheduler = new SynchronizationContextFaultPropagatingTaskScheduler();
在整个代码
Throughout code
Task.Factory.StartNew(_ =>
{
// ... task impl here ...
},
CancellationToken.None // your specific cancellationtoken here (if any)
TaskCreationOptions.None, // your proper options here
MyFaultPropagatingTaskScheduler);
这篇关于我怎样才能让任务例外传播回UI线程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!