Ok Joe... 如约而至,以下是如何使用自定义的 TaskScheduler
subclass 通用解决此问题的方法。我已经测试了这个实现,它运行得很好。 别忘了 如果你想看到Application.ThreadException
的实际触发,就不能使用调试器来进行调试!!!
自定义 TaskScheduler
这个自定义 TaskScheduler 实现在 "出生" 时与特定的 SynchronizationContext
绑定,并且将每个需要执行的传入的 Task
,连接一个 Continuation,仅在逻辑 Task
错误时触发。当这个 Continuation 被触发后,它会 Post
回到 SynchronizationContext,在那里抛出故障的 Task
的异常。
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)
{
task.ContinueWith(antecedent =>
{
this.synchronizationContext.Post(sendState =>
{
throw (Exception)sendState;
},
antecedent.Exception);
},
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
this.taskQueue.Enqueue(task);
this.EnsureTasksAreBeingExecuted();
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return false;
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return this.taskQueue.ToArray();
}
#endregion
#region Helper methods
private void EnsureTasksAreBeingExecuted()
{
if(this.taskQueue.Count > 0)
{
ThreadPool.UnsafeQueueUserWorkItem(_ =>
{
Task nextTask;
while(this.taskQueue.TryDequeue(out nextTask))
{
base.TryExecuteTask(nextTask);
}
},
null);
}
}
#endregion
}
关于此实现的一些注意事项/免责声明:
- 如果您使用无参数构造函数,则它将选择当前的SynchronizationContext...,因此,如果您只是在WinForms线程(主表单构造函数等)上构造它,它将自动工作。此外,我还提供了一个构造函数,您可以显式地传递从其他地方获取的SynchronizationContext。
- 我没有提供TryExecuteTaskInline的实现,因此此实现将始终仅将
Task
排队以进行处理。我将这留给读者作为练习。这并不难,只是...没有必要演示您所要求的功能。
- 我正在使用一种简单/基础的方法来安排/执行利用线程池的任务。确实可以有更丰富的实现方式,但是再次强调,这个实现的重点仅在于将异常传递回“应用程序”线程。
好的,现在您有几个选项可以使用此TaskScheduler:
预配置TaskFactory实例
这种方法允许您一次设置一个TaskFactory
,然后使用该工厂实例启动任何任务都将使用自定义的TaskScheduler
。它基本上看起来像这样:
在应用程序启动时
private static readonly TaskFactory MyTaskFactory = new TaskFactory(new SynchronizationContextFaultPropagatingTaskScheduler());
整个代码中
MyTaskFactory.StartNew(_ =>
{
// ... task impl here ...
});
每次调用显式的 TaskScheduler
另一种方法是仅创建自定义 TaskScheduler
的实例,然后每次启动任务时将其传递给默认的 TaskFactory
上的 StartNew
。
在应用程序启动时
private static readonly SynchronizationContextFaultPropagatingTaskScheduler MyFaultPropagatingTaskScheduler = new SynchronizationContextFaultPropagatingTaskScheduler()
代码中
Task.Factory.StartNew(_ =>
{
// ... task impl here ...
},
CancellationToken.None // your specific cancellationtoken here (if any)
TaskCreationOptions.None, // your proper options here
MyFaultPropagatingTaskScheduler);