如何捕获/观察从Task抛出的未处理exception

我正在尝试在我的应用程序中记录/报告所有未处理的exception(错误报告解决方案)。 我遇到过一个总是未处理的情景。 我想知道如何以未处理的方式捕获此错误。 请注意,我今天早上做了很多研究并尝试了很多东西..是的,我已经看过这个 , 这个以及更多。 我只是在寻找一种通用的解决方案来记录未处理的exception。

我在控制台测试应用程序主要方法中有以下代码:

Task.Factory.StartNew(TryExecute); 

要么

 Task.Run((Action)TryExecute); 

以及以下方法:

 private static void TryExecute() { throw new Exception("I'm never caught"); } 

我已经尝试在我的应用程序中连接到以下内容,但它们从未被调用过。

 AppDomain.CurrentDomain.UnhandledException TaskScheduler.UnobservedTaskException 

在我最初发现此错误的Wpf应用程序中,我也连接到这些事件,但它从未被调用过。

 Dispatcher.UnhandledException Application.Current.DispatcherUnhandledException System.Windows.Forms.Application.ThreadException 

唯一被称为的处理程序是:

 AppDomain.CurrentDomain.FirstChanceException 

但这不是一个有效的解决方案,因为我只想报告未捕获的exception(并非每个exception,因为在执行/解析任何catch块之前调用FirstChanceException。

如上所述, TaskScheduler.UnobservedTaskException事件应该为您提供所需的内容。 是什么让你觉得它没有被解雇?

在特定情况下,exception会被任务捕获,然后重新抛出, 但不会立即重新抛出。 任务的exception会以多种方式重新抛出(在我的脑海中,可能还有更多)。

  1. 当您尝试访问结果时( Task.Result
  2. 在任务上调用Wait()Task.WaitOne()Task.WaitAll()或其他相关的Wait方法。
  3. 当您尝试在不明确查看或处理exception的情况下处置Task时

如果您执行上述任何操作,则将在运行代码的任何线程上重新抛出exception, 并且由于您将观察exception, 因此将不会调用该事件 。 如果你没有try {} catch {}的代码,你将触发AppDomain.CurrentDomain.UnhandledException ,这听起来像是可能发生的事情。

重新抛出exception的另一种方式是:

  • 当您不执行上述任何操作时,任务仍然会将exception视为未观察到,并且任务正在完成。 它是作为最后的努力让你知道有一个你没有看到的例外。

如果是这种情况,并且由于终结器是非确定性的,您是否在等待GC发生,以便将具有未观察到的exception的任务放入终结器队列中,然后再等待它们最终确定?

编辑: 这篇文章谈到了这一点。 本文将讨论事件存在的原因,这可能会让您深入了解如何正确使用事件。

我使用MSDN中的LimitedTaskScheduler来捕获所有exception,包括使用TPL的其他线程:

public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler { /// Whether the current thread is processing work items. [ThreadStatic] private static bool currentThreadIsProcessingItems; /// The list of tasks to be executed. private readonly LinkedList tasks = new LinkedList(); // protected by lock(tasks) private readonly ILogger logger; /// The maximum concurrency level allowed by this scheduler. private readonly int maxDegreeOfParallelism; /// Whether the scheduler is currently processing work items. private int delegatesQueuedOrRunning; // protected by lock(tasks) public LimitedConcurrencyLevelTaskScheduler(ILogger logger) : this(logger, Environment.ProcessorCount) { } public LimitedConcurrencyLevelTaskScheduler(ILogger logger, int maxDegreeOfParallelism) { this.logger = logger; if (maxDegreeOfParallelism Gets the maximum concurrency level supported by this scheduler. public override sealed int MaximumConcurrencyLevel { get { return maxDegreeOfParallelism; } } /// Queues a task to the scheduler. /// The task to be queued. protected sealed override void QueueTask(Task task) { // Add the task to the list of tasks to be processed. If there aren't enough // delegates currently queued or running to process tasks, schedule another. lock (tasks) { tasks.AddLast(task); if (delegatesQueuedOrRunning >= maxDegreeOfParallelism) { return; } ++delegatesQueuedOrRunning; NotifyThreadPoolOfPendingWork(); } } /// Attempts to execute the specified task on the current thread. /// The task to be executed. /// /// Whether the task could be executed on the current thread. protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { // If this thread isn't already processing a task, we don't support inlining if (!currentThreadIsProcessingItems) { return false; } // If the task was previously queued, remove it from the queue if (taskWasPreviouslyQueued) { TryDequeue(task); } // Try to run the task. return TryExecuteTask(task); } /// Attempts to remove a previously scheduled task from the scheduler. /// The task to be removed. /// Whether the task could be found and removed. protected sealed override bool TryDequeue(Task task) { lock (tasks) { return tasks.Remove(task); } } /// Gets an enumerable of the tasks currently scheduled on this scheduler. /// An enumerable of the tasks currently scheduled. protected sealed override IEnumerable GetScheduledTasks() { var lockTaken = false; try { Monitor.TryEnter(tasks, ref lockTaken); if (lockTaken) { return tasks.ToArray(); } else { throw new NotSupportedException(); } } finally { if (lockTaken) { Monitor.Exit(tasks); } } } protected virtual void OnTaskFault(AggregateException exception) { logger.Error(exception); } /// /// Informs the ThreadPool that there's work to be executed for this scheduler. /// private void NotifyThreadPoolOfPendingWork() { ThreadPool.UnsafeQueueUserWorkItem(ExcuteTask, null); } private void ExcuteTask(object state) { // Note that the current thread is now processing work items. // This is necessary to enable inlining of tasks into this thread. currentThreadIsProcessingItems = true; try { // Process all available items in the queue. while (true) { Task item; lock (tasks) { // When there are no more items to be processed, // note that we're done processing, and get out. if (tasks.Count == 0) { --delegatesQueuedOrRunning; break; } // Get the next item from the queue item = tasks.First.Value; tasks.RemoveFirst(); } // Execute the task we pulled out of the queue TryExecuteTask(item); if (!item.IsFaulted) { continue; } OnTaskFault(item.Exception); } } finally { // We're done processing items on the current thread currentThreadIsProcessingItems = false; } } }
public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler { /// Whether the current thread is processing work items. [ThreadStatic] private static bool currentThreadIsProcessingItems; /// The list of tasks to be executed. private readonly LinkedList tasks = new LinkedList(); // protected by lock(tasks) private readonly ILogger logger; /// The maximum concurrency level allowed by this scheduler. private readonly int maxDegreeOfParallelism; /// Whether the scheduler is currently processing work items. private int delegatesQueuedOrRunning; // protected by lock(tasks) public LimitedConcurrencyLevelTaskScheduler(ILogger logger) : this(logger, Environment.ProcessorCount) { } public LimitedConcurrencyLevelTaskScheduler(ILogger logger, int maxDegreeOfParallelism) { this.logger = logger; if (maxDegreeOfParallelism Gets the maximum concurrency level supported by this scheduler. public override sealed int MaximumConcurrencyLevel { get { return maxDegreeOfParallelism; } } /// Queues a task to the scheduler. /// The task to be queued. protected sealed override void QueueTask(Task task) { // Add the task to the list of tasks to be processed. If there aren't enough // delegates currently queued or running to process tasks, schedule another. lock (tasks) { tasks.AddLast(task); if (delegatesQueuedOrRunning >= maxDegreeOfParallelism) { return; } ++delegatesQueuedOrRunning; NotifyThreadPoolOfPendingWork(); } } /// Attempts to execute the specified task on the current thread. /// The task to be executed. /// /// Whether the task could be executed on the current thread. protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { // If this thread isn't already processing a task, we don't support inlining if (!currentThreadIsProcessingItems) { return false; } // If the task was previously queued, remove it from the queue if (taskWasPreviouslyQueued) { TryDequeue(task); } // Try to run the task. return TryExecuteTask(task); } /// Attempts to remove a previously scheduled task from the scheduler. /// The task to be removed. /// Whether the task could be found and removed. protected sealed override bool TryDequeue(Task task) { lock (tasks) { return tasks.Remove(task); } } /// Gets an enumerable of the tasks currently scheduled on this scheduler. /// An enumerable of the tasks currently scheduled. protected sealed override IEnumerable GetScheduledTasks() { var lockTaken = false; try { Monitor.TryEnter(tasks, ref lockTaken); if (lockTaken) { return tasks.ToArray(); } else { throw new NotSupportedException(); } } finally { if (lockTaken) { Monitor.Exit(tasks); } } } protected virtual void OnTaskFault(AggregateException exception) { logger.Error(exception); } /// /// Informs the ThreadPool that there's work to be executed for this scheduler. /// private void NotifyThreadPoolOfPendingWork() { ThreadPool.UnsafeQueueUserWorkItem(ExcuteTask, null); } private void ExcuteTask(object state) { // Note that the current thread is now processing work items. // This is necessary to enable inlining of tasks into this thread. currentThreadIsProcessingItems = true; try { // Process all available items in the queue. while (true) { Task item; lock (tasks) { // When there are no more items to be processed, // note that we're done processing, and get out. if (tasks.Count == 0) { --delegatesQueuedOrRunning; break; } // Get the next item from the queue item = tasks.First.Value; tasks.RemoveFirst(); } // Execute the task we pulled out of the queue TryExecuteTask(item); if (!item.IsFaulted) { continue; } OnTaskFault(item.Exception); } } finally { // We're done processing items on the current thread currentThreadIsProcessingItems = false; } } } 

而不是使用Reflection将TaskScheduler的“注册”作为默认值:

public static class TaskLogging { private const BindingFlags StaticBinding = BindingFlags.Static | BindingFlags.NonPublic; public static void SetScheduler(TaskScheduler taskScheduler) { var field = typeof(TaskScheduler).GetField("s_defaultTaskScheduler", StaticBinding); field.SetValue(null, taskScheduler); SetOnTaskFactory(new TaskFactory(taskScheduler)); } private static void SetOnTaskFactory(TaskFactory taskFactory) { var field = typeof(Task).GetField("s_factory", StaticBinding); field.SetValue(null, taskFactory); } }
public static class TaskLogging { private const BindingFlags StaticBinding = BindingFlags.Static | BindingFlags.NonPublic; public static void SetScheduler(TaskScheduler taskScheduler) { var field = typeof(TaskScheduler).GetField("s_defaultTaskScheduler", StaticBinding); field.SetValue(null, taskScheduler); SetOnTaskFactory(new TaskFactory(taskScheduler)); } private static void SetOnTaskFactory(TaskFactory taskFactory) { var field = typeof(Task).GetField("s_factory", StaticBinding); field.SetValue(null, taskFactory); } }