Catch UnhandledExceptions raised on Threads, Tasks, Timers, ThreadPool threads, and BackgroundWorkers

StackOverflow https://stackoverflow.com/questions/21141549

문제

I have a WPF C# 4.0 application that I'm using to investigate unhandled exception handling. I would like to have just one place in my code that handles unhandled exceptions raised on any thread no matter how the thread was created.In my App.xaml.cs file, I have these unhandled exception handlers that spit out messages to the console:

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");
    }
}

I have a single WPF window with five buttons on it that start threads in five different ways: Task, Thread, ThreadPool, BackgroundWorker, and System.Threading.Timer. The code for each button click starts a new task, thread, etc. respectively and throws a NotImplementedException on the new thread. In some cases the exception is caught, in others, the exception is not caught.

Here are my click handlers with code comments that show whether the exception thrown on that thread is caught:

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);
}

This behavior seems inconsistent to me.Why do Timers, Thread objects, and ThreadPool threads catch the exception whereas BackgroundWorkers and Tasks don't? I use mostly Tasks, Timers, and BackgroundWorkers in my code, so I experience this inconsistency.Does the MSDN have some documentation that explains how WPF handles unhandled exceptions on threads created in these five different ways?

Edit: I tried a sixth thread creation method using Action.BeginInvoke(). This appears to act similarly to a Task. The code is here:

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) { }

Edit #2: I looked at parallel loops that throw exceptions. It looks like any exception that is raised in the two parallel loops below will be caught:

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
        });
}
도움이 되었습니까?

해결책

After searching around more and testing, here is how I will handle exceptions raised on other threads. For unhandled exceptions, ensure that I make the following call at the top of Application.OnStartup():

AppDomain.CurrentDomain.UnhandledException += 
    this.CurrentDomain_UnhandledException;

For creating threads, I'll prefer to use System.Threading.Thread, System.Threading.ThreadPool.QueueUserWorkItem, or System.Threading.Timer. I find BackgroundWorkers useful, so I extended the class in a way that will make it easier to handle unhandled exceptions:

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

I can use IEnumerable.AsParallel or System.Threading.Tasks.Parallel.For for parallel loops because the AppDomain.CurrentDomain.UnhandledException will catch their exceptions as well.

I will avoid Tasks. I will also avoid Action<T>s, and Func<T>s if I call BeginInvoke on them. I'll avoid these because AppDomain.CurrentDomain.UnhandledException does not catch their exceptions.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top