捕获在线程,任务,计时器,ThreadPool线程和BackgroundWorkers上引发的UnhandledExceptions

时间:2014-01-15 15:30:55

标签: wpf multithreading c#-4.0 unhandled-exception

我有一个WPF C#4.0应用程序,我用它来调查未处理的异常处理。我希望在我的代码中只有一个地方处理在任何线程上引发的未处理异常,无论线程是如何创建的。在我的App.xaml.cs文件中,我有这些未处理的异常处理程序,它们向控制台吐出消息:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        AppDomain.CurrentDomain.UnhandledException += this.CurrentDomain_UnhandledException;
        Application.Current.DispatcherUnhandledException += this.Current_DispatcherUnhandledException;
    }

    void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
    {
        Console.WriteLine("Current_DispatcherUnhandledException");
        e.Handled = true;
    }

    void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Console.WriteLine("CurrentDomain_UnhandledException");
    }
}

我有一个带有五个按钮的WPF窗口,它以五种不同的方式启动线程:Task,Thread,ThreadPool,BackgroundWorker和System.Threading.Timer。每个按钮单击的代码分别启动一个新任务,线程等,并在新线程上抛出NotImplementedException。在某些情况下会捕获异常,在其他情况下,异常不会被捕获。

以下是我的点击处理程序,其代码注释显示是否捕获了该线程上抛出的异常:

private void Button_Task_Click(object sender, RoutedEventArgs e)
{
    System.Threading.Tasks.Task.Factory.StartNew(() =>
        {
            throw new NotImplementedException(); //not caught
        });
}

private void Button_Thread_Click(object sender, RoutedEventArgs e)
{
    var thread = new System.Threading.Thread(
        new System.Threading.ParameterizedThreadStart((x) =>
        {
            throw new NotImplementedException();
            //CurrentDomain_UnhandledException caught
        }));
    thread.Start();
}

private void Button_ThreadPool_Click(object sender, RoutedEventArgs e)
{
    System.Threading.ThreadPool.QueueUserWorkItem(
        new System.Threading.WaitCallback((x) =>
        {
            throw new NotImplementedException();
            //CurrentDomain_UnhandledException caught
        }));
}

private void Button_BWorker_Click(object sender, RoutedEventArgs e)
{
    var bg = new System.ComponentModel.BackgroundWorker();
    bg.DoWork += new System.ComponentModel.DoWorkEventHandler(bg_DoWork);
    bg.RunWorkerAsync();
}
private void bg_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
    throw new NotImplementedException(); //not caught
}

private void Button_Timer_Click(object sender, RoutedEventArgs e)
{
    var timer = new System.Threading.Timer((x) =>
        {
            throw new NotImplementedException();
            //CurrentDomain_UnhandledException caught
        }, null, 0, System.Threading.Timeout.Infinite);
}

这种行为似乎与我不一致。为什么Timers,Thread对象和ThreadPool线程会捕获异常,而BackgroundWorkers和Tasks却没有?我在代码中主要使用了Tasks,Timers和BackgroundWorkers,所以我遇到了这种不一致.MSDN有一些文档可以解释WPF如何处理以这五种不同方式创建的线程上的未处理异常吗?

编辑:我尝试使用Action.BeginInvoke()创建第六个线程创建方法。这似乎与任务类似。代码在这里:

private void Button_Action_Click(object sender, RoutedEventArgs e)
{
    Action action = () =>
        {
            throw new NotImplementedException();
            //not caught
        };
    action.BeginInvoke(this.DoNothing, null);
}
private void DoNothing(object arg) { }

编辑#2:我查看了抛出异常的并行循环。看起来在下面的两个并行循环中引发的任何异常都将被捕获:

private void Button_AsParallel_Click(object sender, RoutedEventArgs e)
{
    const int loops = 20;
    var list = new List<int>();
    for (int i = 0; i < loops; i++)
    {
        list.Add(i);
    }

    foreach (var item in list.AsParallel().Select(s => s))
    {
        if (item == 15)
            throw new NotImplementedException();
            //CurrentDomain_UnhandledException caught
    }
}

private void Button_ParallelFor_Click(object sender, RoutedEventArgs e)
{
    const int loops = 100;
    var list = new List<int>();
    for (int i = 0; i < loops; i++)
    {
        list.Add(i);
    }

    System.Threading.Tasks.Parallel.For(0, loops, (int i) =>
        {
            if (list[i] == 90)
                throw new NotImplementedException();
                //CurrentDomain_UnhandledException caught
        });
}

1 个答案:

答案 0 :(得分:0)

在搜索更多内容并进行测试后,以下是我将如何处理在其他线程上引发的异常。对于未处理的异常,请确保我在Application.OnStartup()的顶部进行以下调用:

AppDomain.CurrentDomain.UnhandledException += 
    this.CurrentDomain_UnhandledException;

为了创建线程,我更倾向于使用System.Threading.ThreadSystem.Threading.ThreadPool.QueueUserWorkItemSystem.Threading.Timer。我发现BackgroundWorker很有用,所以我以一种更容易处理未处理异常的方式扩展了类:

public class MyBackgroundWorker : BackgroundWorker
{
    protected override void OnRunWorkerCompleted(RunWorkerCompletedEventArgs e)
    {
        if (e.Error != null)
            throw e.Error;
        else
            base.OnRunWorkerCompleted(e);
    }
}

我可以使用IEnumerable.AsParallelSystem.Threading.Tasks.Parallel.For进行并行循环,因为AppDomain.CurrentDomain.UnhandledException也会捕获它们的异常。

我会避免Tasks。如果我打电话给Action<T>,我也会避免Func<T>BeginInvoke s。我会避免这些,因为AppDomain.CurrentDomain.UnhandledException 会抓住他们的例外。