如何将线程异常传播回应用程序异常处理程序

时间:2013-07-01 18:32:08

标签: c# wpf

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

Why exceptions are not propagated by WPF Dispatcher.Invoke?

How can I allow Task exceptions to propagate back to the UI thread?

在下面的代码中,我需要将在任务及其延续中抛出的异常传播回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);
    }
}

5 个答案:

答案 0 :(得分:3)

当您使用StartNewContinueWith时,会对返回的Task发出任何例外情况。

编组异常有两个问题:

  1. Task.Exception将您的例外包装在AggregateException
  2. 当您稍后抛出异常时(例如,在另一个线程上),原始调用堆栈将丢失。
  3. 对于第一个问题,有些人使用FlattenHandle成员直接与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;
    }
    

答案 1 :(得分:1)

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

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

氪, 布赖恩。

答案 2 :(得分:0)

问题的答案可在此处找到: http://blogs.msdn.com/b/pfxteam/archive/2009/05/31/9674669.aspx

基本上有两种情况:你可以等待任务的情况以及你不能发射和忘记的情况。 在您可以等待任务的情况下,将其包装在问题块中,如问题所示,并重新抛出错误。全局应用程序处理程序将捕获它。

在您无法等待任务的情况下,您必须手动调用记录器。没有应用程序级别处理程序可以捕获错误。有可能会触发TaskScheduler.UnobservedTaskException,但该事件是恕我直言,非常间谍和脆弱,并不是一个好的选择。

答案 3 :(得分: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);
        }
    }

当然,这不再是一种“昙花一现”的方法。如果不希望等待任务,则需要从延续中写入日志文件。

答案 4 :(得分:-1)

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

try{
  await Task.Factory.StartNew(() => throw Exception("hello"));       
}catch{
  // will get exception here
}