c# – 我如何允许任务异常传播回UI线程?

前端之家收集整理的这篇文章主要介绍了c# – 我如何允许任务异常传播回UI线程?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
在TPL中,如果 Task抛出异常,则该异常将被捕获并存储在 Task.Exception中,然后遵循 observed exceptions的所有规则.如果从未被观察到,则最终会在终结器线程上重新抛出并导致该进程崩溃.

有没有办法阻止该任务捕获该异常,只是让它传播?

我感兴趣的任务已经在UI线程上运行(由TaskScheduler.FromCurrentSynchronizationContext提供),我想要将异常转义为可以由我现有的Application.ThreadException处理程序处理.

我基本上希望任务中的未处理的异常在按钮单击处理程序中表现为未处理的异常:立即在UI线程上传播,并由ThreadException处理.

解决方法

Ok Joe …如所承诺的,下面是如何通过自定义TaskScheduler子类来一般性地解决这个问题.我已经测试了这个实现,它的作用就像一个魅力.不要忘记,如果你想看到Application.ThreadException实际上启动,你不能附加调试器!

自定义TaskScheduler

这个自定义TaskScheduler实现被绑定到一个特定的SynchronizationContext在“诞生”,并将采取每个传入的任务,它需要执行,链接一个继续,只会触发逻辑任务故障,当它触发时,它返回到SynchronizationContext,它将从任务中抛出异常.

  1. public sealed class SynchronizationContextFaultPropagatingTaskScheduler : TaskScheduler
  2. {
  3. #region Fields
  4.  
  5. private SynchronizationContext synchronizationContext;
  6. private ConcurrentQueue<Task> taskQueue = new ConcurrentQueue<Task>();
  7.  
  8. #endregion
  9.  
  10. #region Constructors
  11.  
  12. public SynchronizationContextFaultPropagatingTaskScheduler() : this(SynchronizationContext.Current)
  13. {
  14. }
  15.  
  16. public SynchronizationContextFaultPropagatingTaskScheduler(SynchronizationContext synchronizationContext)
  17. {
  18. this.synchronizationContext = synchronizationContext;
  19. }
  20.  
  21. #endregion
  22.  
  23. #region Base class overrides
  24.  
  25. protected override void QueueTask(Task task)
  26. {
  27. // Add a continuation to the task that will only execute if faulted and then post the exception back to the synchronization context
  28. task.ContinueWith(antecedent =>
  29. {
  30. this.synchronizationContext.Post(sendState =>
  31. {
  32. throw (Exception)sendState;
  33. },antecedent.Exception);
  34. },TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
  35.  
  36. // Enqueue this task
  37. this.taskQueue.Enqueue(task);
  38.  
  39. // Make sure we're processing all queued tasks
  40. this.EnsureTasksAreBeingExecuted();
  41. }
  42.  
  43. protected override bool TryExecuteTaskInline(Task task,bool taskWasPrevIoUslyQueued)
  44. {
  45. // Excercise for the reader
  46. return false;
  47. }
  48.  
  49. protected override IEnumerable<Task> GetScheduledTasks()
  50. {
  51. return this.taskQueue.ToArray();
  52. }
  53.  
  54. #endregion
  55.  
  56. #region Helper methods
  57.  
  58. private void EnsureTasksAreBeingExecuted()
  59. {
  60. // 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)
  61. if(this.taskQueue.Count > 0)
  62. {
  63. ThreadPool.UnsafeQueueUserWorkItem(_ =>
  64. {
  65. Task nextTask;
  66.  
  67. // This thread pool thread will be used to drain the queue for as long as there are tasks in it
  68. while(this.taskQueue.TryDequeue(out nextTask))
  69. {
  70. base.TryExecuteTask(nextTask);
  71. }
  72. },null);
  73. }
  74. }
  75.  
  76. #endregion
  77. }

有关此实施的注释/免责声明:

>如果使用无参数的构造函数,它将会拾取当前的SynchronizationContext …,所以如果你只是在一个WinForms线程(主窗体构造函数,无论什么)构造它,它将自动工作.奖金,我也有一个构造函数,你可以在其中明确地传入你从别的地方得到的SynchronizationContext.
>我没有提供TryExecuteTaskInline的实现,所以这个实现将只是排队要处理的任务.我把这作为读者的练习.这不难,只是…没有必要展示你要求的功能.
>我使用简单/原始的方法来调度/执行利用ThreadPool的任务.肯定有更丰富的实现,但是这个实现的重点只是将异常封装到“应用程序”线程

好的,现在你有几个选项来使用这个TaskScheduler:

预配置TaskFactory实例

方法允许您一次设置TaskFactory,然后从该工厂实例开始的任何任务将使用自定义TaskScheduler.这基本上看起来像这样:

在应用程序启动时

  1. private static readonly TaskFactory MyTaskFactory = new TaskFactory(new SynchronizationContextFaultPropagatingTaskScheduler());

整个代码

  1. MyTaskFactory.StartNew(_ =>
  2. {
  3. // ... task impl here ...
  4. });

显式TaskScheduler每次呼叫

另一种方法是创建自定义TaskScheduler的实例,然后在每次启动任务时将其传递到默认的TaskFactory上的StartNew中.

在应用程序启动时

  1. private static readonly SynchronizationContextFaultPropagatingTaskScheduler MyFaultPropagatingTaskScheduler = new SynchronizationContextFaultPropagatingTaskScheduler();

整个代码

  1. Task.Factory.StartNew(_ =>
  2. {
  3. // ... task impl here ...
  4. },CancellationToken.None // your specific cancellationtoken here (if any)
  5. TaskCreationOptions.None,// your proper options here
  6. MyFaultPropagatingTaskScheduler);

猜你在找的C#相关文章