JSQuareD
JSQuareD

Reputation: 4786

What Exception to throw when thread exits unexpectedly?

I have a worker thread which, when it terminates, signals an event. This event is then marshalled over to the main thread to notifiy it of the worker thread's termination. When the worker thread encounters an unhandled exception, I want this exception to be handled by the main thread's error handling system. Therefor, the worker thread sets a property indicating its unexpected termination, and saves the exception in another property, then signals the event and exits.

After the event has been marshalled over to the main thread, I want to throw a new exception with the original exception set as the inner exception. My question is: what should the type of this new Exception be? Is there a specific System.somethingException for this kind of situation, should I Design my own Exception class for this specific situation, or would throwing a standard System.Exception with a proper message be considered appropriate?

C#-psuedo code:

class MyThread
{
    public TerminationState Termination { get; private set; }
    public Exception UncaughtException { get; private set; }

    public delegate void ThreadTerminatedDelegate(MyThread thread);
    public event ThreadTerminatedDelegate ThreadTerminated;

    private void run()
    {
        try
        {
            doSomeWork();
        }
        catch(Exception e)
        {
            UncaughtException = e;
            Termination = TerminationState.AbortOnException;
            ThreadTerminated(this);
            return;
        }
        Termination = TerminationState.NormalTermination;
        ThreadTerminated(this);
    }
}

class MainThread
{
    private MyThread myThread = new MyThread();

    private void run()
    {
        myThread.ThreadTerminated += handleTermination;
        myThread.Start();
    }

    private void handleTermination(MyThread thread)
    {
        if (InvokeRequired)
        {
            MyThread.ThreadTerminatedDelegate cb = new MyThread.ThreadTerminatedDelegate(handleTermination);
            BeginInvoke(cb, new object[] { thread });
        }
        else
        {
            if (thread.Termination == TerminationState.AbortOnException)
            {
                if (isFatal(thread.UncaughtException))
                    throw new Exception("", thread.UncaughtException); //what to do here?
                else
                    fixTheProblem();
            }
            else
            {
                //normal wrapping up
            }
        }
    }
}

Upvotes: 4

Views: 1337

Answers (1)

Timothy Schoonover
Timothy Schoonover

Reputation: 3265

I believe that you can perform all necessary exception handling for unhandled background exceptions by performing the background work in a Task and then handling any exceptions in a continuation of that task that is explicitly scheduled to run on the main thread. There are additional options you can specify for the continuation, but this should cover your scenario.

Task.Factory.StartNew(
    () =>
    {
        // Do some work that may throw.
        // This code runs on the Threadpool.
        // Any exceptions will be propagated
        // to continuation tasks and awaiters
        // for observation.
        throw new StackOverflowException(); // :)
    }
).ContinueWith(
    (a) =>
    {
        // Handle your exception here.
        // This code runs on the thread
        // that started the worker task.
        if (a.Exception != null)
        {
            foreach (var ex in a.Exception.InnerExceptions)
            {
                // Try to handle or throw.
            }
        }
    },
    CancellationToken.None,
    TaskContinuationOptions.None,
    TaskScheduler.FromCurrentSynchronizationContext()
);

Another useful link is MSDN's Asyncronous Programming Patterns. It identifies the 3 main ways to implement asynchronous operations in an application. Your current implementation sounds most similar to what the article calls EAP (Event-based Asynchronous Pattern).

I personally prefer TAP (Task-based Asynchronous Pattern) which relies on the .NET 4.0 TPL (Task Parallel Library). It's well worth mastering due to the simplicity of its syntax and its extensive capabilities.

From MSDN:

  • Asynchronous Programming Model (APM) pattern (also called the IAsyncResult pattern), where asynchronous operations require Begin and End methods (for example, BeginWrite and EndWrite for asynchronous write operations). This pattern is no longer recommended for new development. For more information, see Asynchronous Programming Model (APM).
  • Event-based Asynchronous Pattern (EAP), which requires a method that has the Async suffix, and also requires one or more events, event handler delegate types, and EventArg-derived types. EAP was introduced in the .NET Framework 2.0. It is no longer recommended for new development. For more information, see Event-based Asynchronous Pattern (EAP).
  • Task-based Asynchronous Pattern (TAP), which uses a single method to represent the initiation and completion of an asynchronous operation. TAP was introduced in the .NET Framework 4 and is the recommended approach to asynchronous programming in the .NET Framework. For more information, see Task-based Asynchronous Pattern (TAP).

Also, don't forget about the trusty BackgroundWorker class. This class was a staple for me for a long time and although it has become somewhat deprecated by TAP, it will still get the job done and is very easy to understand and use.

// Create a new background worker.
var bgw = new BackgroundWorker();

// Assign a delegate to perform the background work.
bgw.DoWork += (s, e) =>
    {
        // Runs in background thread. Unhandled exceptions
        // will cause the thread to terminate immediately.
        throw new StackOverflowException();
    };

// Assign a delegate to perform any cleanup/error handling/UI updating.
bgw.RunWorkerCompleted += (s, e) =>
    {
        // Runs in UI thread. Any unhandled exception that
        // occur in the background thread will be accessible
        // in the event arguments Error property.
        if (e.Error != null)
        {
            // Handle or rethrow.
        }
    };

// Start the background worker asynchronously.
bgw.RunWorkerAsync();

Upvotes: 1

Related Questions