如何处理使用任务并行库时的所有未处理异常?

42
我正在使用.NET 4.0中的TPL(任务并行库)。 我想通过使用Thread.GetDomain().UnhandledException事件来集中处理所有未处理的异常的处理逻辑。 然而,在我的应用程序中,对于使用TPL代码启动的线程,例如Task.Factory.StartNew(...),该事件从未触发。 如果我使用类似new Thread(threadStart).Start()的东西,则确实会触发该事件。 这篇MSDN文章建议在使用TPL时使用Task.Wait()来捕获AggregateException,但这不是我想要的,因为这种机制不够"集中"。
是否有人完全遇到同样的问题还是只有我? 你有任何解决方案吗?
3个回答

35

我认为你需要的是TaskScheduler.UnobservedTaskException Event

当一个处理失败的Task的未观察到的异常即将触发异常升级策略时,这个事件会发生。默认情况下,异常升级策略会结束该进程。

所以,这个事件类似于你在问题中提到的DomainUnhandledException,但它只发生在任务中。

顺便说一句,未观察到异常策略(是的,这不是未观察到的异常,微软的人发明了新词...再次),从.NET 4.0到.NET 4.5有所改变。在.NET 4.0中,未观察到的异常会导致进程终止,但在.NET 4.5中不会。这全部是因为我们将在C# 5和VB 11中拥有新的异步内容。


谢谢,Sergey。我知道这个,但出于某种原因它对我没起作用。http://www.buunguyen.net/blog/handle-all-uncaught-exceptions-thrown-when-using-task-parallel-library.html#comment-7259 - Buu
9
问题在于UnobservedTaskException事件只有在故障的Task对象被终结时才会被触发。但对象终结并不能保证一定会运行。 - Jean Hominal
非常有帮助的提醒,未观察到的异常策略在.NET 4.5中已经发生了变化。 - stukselbax
我刚在.NET 4.0中进行了一些测试,Task中的未观察到的异常不会导致进程终止。 - hyankov

21
似乎没有内置的方法来处理这个问题(两周后仍然没有答案)。我已经编写了一些自定义代码来解决这个问题。解决方案描述非常冗长,所以我在我的博客中发布了文章。如果您感兴趣,请参考此文章
更新5/7/2010:我找到了更好的方法,利用任务继续。我创建了一个名为ThreadFactory的类,它公开了错误事件,可以由顶级处理程序订阅,并提供启动附加适当继续的任务的方法。
代码发布在这里
更新4/18/2011:根据Nifle的评论,发布博客文章中的代码。
internal class ThreadFactory
{
    public delegate void TaskError(Task task, Exception error);

    public static readonly ThreadFactory Instance = new ThreadFactory();

    private ThreadFactory() {}

    public event TaskError Error;

    public void InvokeError(Task task, Exception error)
    {
        TaskError handler = Error;
        if (handler != null) handler(task, error);
    }

    public void Start(Action action)
    {
        var task = new Task(action);
        Start(task);
    }

    public void Start(Action action, TaskCreationOptions options)
    {
        var task = new Task(action, options);
        Start(task);
    }

    private void Start(Task task)
    {
        task.ContinueWith(t => InvokeError(t, t.Exception.InnerException),
                            TaskContinuationOptions.OnlyOnFaulted |
                            TaskContinuationOptions.ExecuteSynchronously);
        task.Start();
    }
}

1
更新后的解决方案据我所知运行良好。为什么很少有人遇到这个问题? - Helge Klein
如果您能在这里包含您博客中的代码,我将非常感激。 - Nifle
@Buu Nguyen 你好,我根据你的方法在这里做了一些事情:http://stackoverflow.com/questions/11831844/unobservedtaskexception-being-throw-but-it-is-handled-by-a-taskscheduler-unobser/11908212#11908212 非常感谢。希望C#有更好的解决方案。 - newway

12
我看到两个选项可用于TPL的异常处理集中化: 1. 使用任务计划程序的Unobserved Task Exception事件。 2. 对于出错状态的任务,使用连续性。
使用任务计划程序的UnobservedTaskException事件。 任务计划程序有一个UnobservedTaskException事件,您可以使用operator +=进行订阅。
注意事项: 1. 在处理程序的主体中,您需要在UnobservedTaskExceptionEventArgs参数上调用SetObserved(),以通知计划程序已处理异常。 2. 当垃圾收集器收集任务时,将调用处理程序。 3. 如果您等待任务,仍然需要通过try/catch块保护等待。 4. .Net 4.0和4.5中未处理的任务异常的默认策略不同。
总结:此方法适用于fire-and-forget任务和捕获从集中式异常处理策略中逃逸的异常。
对于出错状态的任务,使用连续性。 使用TPL,您可以使用方法ContinueWith()将操作附加到任务,并指定连续选项。此操作将在任务终止后仅在指定的情况下调用。特别是:
    t.ContinueWith(c => { /* exception handling code */ },
                   TaskContinuationOptions.OnlyOnFaulted);

在任务t中安装带有异常处理代码的续集。仅当任务t由于未处理的异常而终止时,此代码才会运行。

  • 注意1:在异常处理代码中获取异常值。否则它将被冒泡出来。
  • 注意2:任务终止后将立即调用异常处理代码。
  • 注意3:如果在异常处理代码中捕获了异常,它将被视为已处理,等待任务的try/catch块将无法捕获它。

我认为更好的集中式异常处理方法是使用从Task继承的自定义任务,并通过续集添加异常处理程序。并且通过使用Task Scheduler的Unobserved Task Exception事件来捕获尝试使用未自定义任务的尝试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接