有没有办法阻止该任务捕获该异常,只是让它传播?
我感兴趣的任务已经在UI线程上运行(由TaskScheduler.FromCurrentSynchronizationContext提供),我想要将异常转义为可以由我现有的Application.ThreadException处理程序处理.
我基本上希望任务中的未处理的异常在按钮单击处理程序中表现为未处理的异常:立即在UI线程上传播,并由ThreadException处理.
解决方法
自定义TaskScheduler
这个自定义TaskScheduler实现被绑定到一个特定的SynchronizationContext在“诞生”,并将采取每个传入的任务,它需要执行,链接一个继续,只会触发逻辑任务故障,当它触发时,它返回到SynchronizationContext,它将从任务中抛出异常.
- 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的实现,所以这个实现将只是排队要处理的任务.我把这作为读者的练习.这不难,只是…没有必要展示你要求的功能.
>我使用简单/原始的方法来调度/执行利用ThreadPool的任务.肯定有更丰富的实现,但是这个实现的重点只是将异常封装到“应用程序”线程
好的,现在你有几个选项来使用这个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);