wpf 如何捕获/观察从任务抛出的未处理异常
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19164556/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to catch/observe an unhandled exception thrown from a Task
提问by Blake Niemyjski
I'm trying to log / report all unhandled exceptions in my app (error reporting solution). I've come across a scenario that is always unhandled. I'm wondering how would I catch this error in an unhandled manner. Please note that I've done a ton of research this morning and tried a lot of things.. Yes, I've seen this, thisand many more. I'm just looking for a generic solution to log unhandled exceptions.
我正在尝试在我的应用程序中记录/报告所有未处理的异常(错误报告解决方案)。我遇到过一个总是无法处理的场景。我想知道如何以未处理的方式捕获此错误。请注意,我今天早上做了大量的研究并尝试了很多东西.. 是的,我已经看到了这个,这个等等。我只是在寻找一个通用的解决方案来记录未处理的异常。
I have the following code inside of a console test apps main method:
我在控制台测试应用程序 main 方法中有以下代码:
Task.Factory.StartNew(TryExecute);
or
或者
Task.Run((Action)TryExecute);
as well as the following method:
以及以下方法:
private static void TryExecute() {
throw new Exception("I'm never caught");
}
I'm already tried wiring up to the following in my app, but they are never called.
我已经尝试在我的应用程序中连接到以下内容,但它们从未被调用。
AppDomain.CurrentDomain.UnhandledException
TaskScheduler.UnobservedTaskException
In my Wpf app where I initially found this error I also wired up to these events but it was never called.
在我最初发现此错误的 Wpf 应用程序中,我也连接到这些事件,但从未调用过它。
Dispatcher.UnhandledException
Application.Current.DispatcherUnhandledException
System.Windows.Forms.Application.ThreadException
The only handler that is called ever is:
唯一被调用的处理程序是:
AppDomain.CurrentDomain.FirstChanceException
but this is not a valid solution as I only want to report uncaught exceptions (not every exception as FirstChanceException is called before any catch blocks are ever executed / resolved.
但这不是一个有效的解决方案,因为我只想报告未捕获的异常(并非每个异常,因为 FirstChanceException 在执行/解决任何 catch 块之前都被调用。
回答by Christopher Currens
The TaskScheduler.UnobservedTaskExceptionevent should give you what you want, as you stated above. What makes you think that it is not getting fired?
如上所述,该TaskScheduler.UnobservedTaskException活动应该为您提供您想要的东西。是什么让你认为它不会被解雇?
Exceptions are caught by the task and then re-thrown, but not immediately, in specific situations. Exceptions from tasks are re-thrown in several ways (off the top of my head, there are probably more).
异常被任务捕获,然后在特定情况下重新抛出,但不会立即抛出。任务的异常以多种方式重新抛出(在我的脑海中,可能还有更多)。
- When you try and access the result (
Task.Result) - Calling
Wait(),Task.WaitOne(),Task.WaitAll()or another relatedWaitmethod on the task. - When you try to dispose the Task without explicitly looking at or handling the exception
- 当您尝试访问结果 (
Task.Result) - 调用
Wait(),Task.WaitOne(),Task.WaitAll()或其他相关Wait的任务的方法。 - 当您尝试在不显式查看或处理异常的情况下处理任务时
If you do any of the above, the exception will be rethrown on whatever thread that code is running on, and the event will not be calledsince you will be observingthe exception. If you don't have the code inside of a try {} catch {}, you will fire the AppDomain.CurrentDomain.UnhandledException, which sounds like what might be happening.
如果您执行上述任何操作,异常将在代码运行的任何线程上重新抛出,并且不会调用该事件,因为您将观察异常。如果您在 a 中没有代码try {} catch {},您将触发AppDomain.CurrentDomain.UnhandledException,这听起来像是可能发生的事情。
The other way the exception is re-thrown would be:
重新抛出异常的另一种方式是:
- When you do none of the above so that the task still views the exception as unobserved and the Task is getting finalized. It is thrown as a last ditch effort to let you know there was an exception that you didn't see.
- 当您不执行上述任何操作时,任务仍将异常视为未观察到,并且任务正在最终确定。它被抛出作为最后的努力,让您知道有一个您没有看到的异常。
If this is the case and since the finalizer is non-deterministic, are you waiting for a GC to happen so that those tasks with unobserved exceptions are put in the finalizer queue, and then waiting again for them to be finalized?
如果是这种情况并且由于终结器是不确定的,您是否在等待 GC 发生以便将那些具有未观察到的异常的任务放入终结器队列,然后再次等待它们被终结?
EDIT: This articletalks a little bit about this. And this articletalks about why the event exists, which might give you insight into how it can be used properly.
回答by Matteo Migliore
I used the LimitedTaskSchedulerfrom MSDN to catch all exceptions, included from other threads using the TPL:
我使用MSDN 中的LimitedTaskScheduler来捕获所有异常,包括使用 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;
}
}
}
And than the "registration" of the TaskScheduler as the default using 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);
}
}

