好吧,乔...正如所承诺的,以下是如何使用自定义解决此问题的方法TaskScheduler
子类。我已经测试了这个实现,它的效果非常好。别忘了如果您想查看,则无法附加调试器Application.ThreadException
真的要火了!!!
自定义任务调度程序
这个自定义的 TaskScheduler 实现与特定的SynchronizationContext
在“出生”时,将采取每个传入Task
它需要执行,将一个 Continuation 链接到它上面,只有在逻辑满足时才会触发Task
故障,当它着火时,它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)
{
// 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 的实现,因此这个实现总是只是将
Task
有待研究。我将其作为练习留给读者。这并不难,只是......没有必要演示您所要求的功能。
- 我正在使用一种简单/原始的方法来调度/执行利用线程池的任务。肯定有更丰富的实现,但是这个实现的重点只是将异常封送回“应用程序”线程
好的,现在您有几个使用此 TaskScheduler 的选项:
预配置 TaskFactory 实例
这种方法允许您设置TaskFactory
一次之后,您从该工厂实例开始的任何任务都将使用自定义TaskScheduler
。基本上看起来像这样:
在应用程序启动时
private static readonly TaskFactory MyTaskFactory = new TaskFactory(new SynchronizationContextFaultPropagatingTaskScheduler());
整个代码
MyTaskFactory.StartNew(_ =>
{
// ... task impl here ...
});
每次调用的显式任务调度程序
另一种方法是只创建自定义的实例TaskScheduler
然后将其传递到StartNew
在默认情况下TaskFactory
每次你开始一项任务时。
在应用程序启动时
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);