2

http://msdn.microsoft.com/en-us/magazine/gg598924.aspx

为什么 WPF Dispatcher.Invoke 不传播异常?

如何允许任务异常传播回 UI 线程?

在下面的代码中,我需要将任务中抛出的异常及其延续传播到 ui 线程,在那里它们将由 LogException 处理。如果我需要在某处重新抛出异常,那对我来说很好。什么都行。我怎么做?我引用了一些与我的类似的问题,但我没有看到与我的应用程序相关的答案。
编辑3: 发布了一个简化的例子

编辑2: 见: http: //msdn.microsoft.com/en-us/library/dd997415 (v=vs.100).aspx

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Loaded += new RoutedEventHandler(MainWindow_Loaded);
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        FireAndForget();
        WaitOnTask();
    }

    private void FireAndForget()
    {
        Task t1 = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(3000);
            throw new Exception("boo"); 
        });

        Task c1 = t1.ContinueWith((t) =>
            {
                // The app global exception handler will not catch this.

            }, TaskContinuationOptions.OnlyOnFaulted);

        //MessageBox.Show("Task is running");
    }


    private void WaitOnTask()
    {
        Task t1 = Task.Factory.StartNew(() =>
        {
            throw new Exception("boo");
        });

        try
        {
            t1.Wait();
        }
        catch (Exception ex)
        {
            // The app global exception handler will catch this:
            throw new Exception("Task", ex);
        }
    }
}



public partial class App : Application
{
    public App()
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        Application.Current.DispatcherUnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(Current_DispatcherUnhandledException);
        //System.Threading.Tasks.TaskScheduler.UnobservedTaskException += new EventHandler<System.Threading.Tasks.UnobservedTaskExceptionEventArgs>(TaskScheduler_UnobservedTaskException);
    }

    void TaskScheduler_UnobservedTaskException(object sender, System.Threading.Tasks.UnobservedTaskExceptionEventArgs e)
    {
        LogException(e.Exception);
    }

    void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
    {
        LogException(e.Exception);
    }

    void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        LogException(e.ExceptionObject as Exception);
    }

    private void LogException(Exception ex)
    {
        // log it
        string error = "This app has encountered an unexpected error .  The error message is:" + Environment.NewLine + ex.Message + Environment.NewLine;
        Exception tmp = ex.InnerException;

        while (tmp != null)
        {
            error += "Inner exception is: " + Environment.NewLine + tmp.Message + Environment.NewLine;
            tmp = tmp.InnerException;
        }

        error += "Please press OK to exit.";
        MessageBox.Show(error, "Error");
        Environment.Exit(-1);
    }
}
4

5 回答 5

3

当您使用StartNeworContinueWith时,任何异常都会放在返回的Task.

封送异常有两个问题:

  1. Task.Exception将您的异常包装在AggregateException.
  2. 当您稍后抛出异常时(例如,在另一个线程上),原始调用堆栈将丢失。

对于第一个问题,有些人使用FlattenorHandle成员直接使用AggregateException. 我更喜欢通过处理Task.Exception.InnerException而不是Task.Exception.

对于第二个问题,有些人通过将其包装在另一个异常中来解决它,但我采取了另一种方法。.NET 4.5 引入ExceptionDispatchInfo,这是正确的方法。在 .NET 4.0 中,您可以破解如下内容:

public static Exception Rethrow(this Exception ex)
{
  typeof(Exception).GetMethod("PrepForRemoting",
      BindingFlags.NonPublic | BindingFlags.Instance)
      .Invoke(ex, new object[0]);
  throw ex;
}
于 2013-07-01T19:18:02.193 回答
1

我不确定我是否在这里遗漏了一些东西,但是如果您使用 TaskScheduler.FromCurrentSynchronizationContext() 作为 ContinueWith 的第二个参数,那么它将被编组回您的 UX 线程。

如果您想要更多示例,我实际上写了一篇关于它的博客文章。 http://www.briankeating.net/post/Why-I-love-the-Task-library

克鲁,布赖恩。

于 2014-01-08T21:32:01.887 回答
0

要在代码中传播异常,您需要Wait在所有任务上进行。如果您对FireAndForget方法进行以下更改Exception,则嵌套中的Task将传播回调用线程。

    private void FireAndForget()
    {
        var tasks = new Task[2];
        tasks[0] = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(3000);
            throw new Exception("boo");
        });

        tasks[1] = tasks[0].ContinueWith((t) =>
        {
            throw new Exception("nested boo", tasks[0].Exception);

        }, TaskContinuationOptions.OnlyOnFaulted);

        try
        {
            Task.WaitAll(tasks);
        }
        catch (AggregateException ex)
        {
            throw new Exception("Task", ex);
        }
    }

当然,这不再是“一劳永逸”的方法。如果不希望等待任务,则需要从 continuation 中写入日志文件。

于 2013-07-02T20:09:30.717 回答
0

The answer to the question is found here: http://blogs.msdn.com/b/pfxteam/archive/2009/05/31/9674669.aspx

Basically there are two scenarios: Situations where you can wait on the task and situations where you cannot i.e. fire and forget. In situations where you can wait on the task, wrap it in a try block as shown in the question and rethrow the error. The global app handler will catch it.

In situtions where you cannot wait on the task you have to call your logger manually. There is no application level handler that will catch the error. There is a possibility that TaskScheduler.UnobservedTaskException will fire, however that event is IMHO highly circumstantial and fragile and not a good option.

于 2013-07-02T19:26:09.473 回答
-1

您可以await在任务完成后从任务代码中接收异常。

try{
  await Task.Factory.StartNew(() => throw Exception("hello"));       
}catch{
  // will get exception here
}
于 2013-07-01T18:34:20.297 回答