Thread and Asynchronous Callback Exceptions

What happens if an exception occurs in another thread of the current AppDomain or in an asynchronous callback? Two samples will illustrate what happens when an exception occurs in an asynchronous callback or in another thread.

Asynchronous Callback Exceptions

At first, you might not consider exceptions in the context of an asynchronous callback. However, when you try it, it seems that there is a bug because it does not seem to work as you planned. Perhaps you were under the false impression that asynchronous callbacks simply use the ThreadPool, and the ThreadPool is a simple collection of Threads. It is more complex than that. The following sample application will help you understand what is happening with exceptions and asynchronous callbacks in general. Listings 15.2415.25 illustrate the important aspects of this sample. The full source for this sample is in the ThreadExceptionsAsyncCallback directory.

Listing 15.24. Setting the Delegate for the Asynchronous Callback
static int WorkerThatThrowsException()
{
    Console.WriteLine("Inside WorkerThatThrowsException on thread {0} ", AppDomain
.GetCurrentThreadId());
    throw new ApplicationException("Exception raised in WorkerThatThrowsException");
    Console.WriteLine("Inside WorkerThatThrowsException about to return the answer");
    return 0;
}
. . .
static void UnprotectedEndInvokeCallback(IAsyncResult iar)
{
    Console.WriteLine("Inside EndInvokeCallback on thread {0} ", AppDomain
.GetCurrentThreadId());
    WorkerDelegate wd = (WorkerDelegate)iar.AsyncState;
    int result = wd.EndInvoke(iar);
    Console.WriteLine("Inside EndInvokeCallback the answer was {0} ", result);
}
. . .
WorkerDelegate wd = new WorkerDelegate(WorkerThatThrowsException);
wd.BeginInvoke(new AsyncCallback(UnprotectedEndInvokeCallback), wd);

This first listing registers a callback called UnprotectedEndInvokeCallback. This callback illustrates the wrong way of handling an asynchronous callback if exceptions are expected. The output is shown next:

Inside Main on thread 2052
Inside WorkerThatThrowsException on thread 1072
Inside UnprotectedEndInvokeCallback on thread 1072

Clearly, the delegate WorkerThatThrowsException throws an exception, but where does it go? It seems that an exception was thrown because the Console output after the exception is thrown does not appear. Unhandled exception handlers have not been installed, so by all rights, this sample should pop up a dialog box indicating an unhandled exception. The callback routine was then changed to wrap the EndInvoke call with a set of try/catch blocks thanks to the advice from the [email protected] mailing list. Now the routine (renamed to EndInvokeCallback) looks like what is shown in Listing 15.25.

Listing 15.25. Modified Delegate for the Asynchronous Callback
static void EndInvokeCallback(IAsyncResult iar)
{
    Console.WriteLine("Inside EndInvokeCallback on thread {0} ", AppDomain
.GetCurrentThreadId());
    WorkerDelegate wd = (WorkerDelegate)iar.AsyncState;
    try
    {
        int result = wd.EndInvoke(iar);
        Console.WriteLine("Inside EndInvokeCallback the answer was {0} ", result);
    }
    catch (Exception e)
    {
        Console.WriteLine("Caught exception in EndInvokeCallback:");
        Console.WriteLine(e.ToString());
    }
}
. . .
wd.BeginInvoke(new AsyncCallback(EndInvokeCallback), wd);

By wrapping a try/catch around the EndInvoke, the exception could be caught. The output looks like this:

Caught exception in EndInvokeCallback:
System.ApplicationException: Exception raised in WorkerThatThrowsException

ServerStackTrace:
   at ThreadExceptions.ExceptionHandlers.WorkerThatThrowsException() in asynccallback.cs
:line 13
   at System.Runtime.Remoting.Messaging.StackBuilderSink.PrivateP rocessMessage 
(MethodBase mb, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext,
 Object[]& outArgs)
   at System.Runtime.Remoting.Messaging.StackBuilderSink.AsyncProcessMessage (IMessage msg
, IMessageSink replySink)

Exception rethrown at [0]:
   at System.Runtime.Remoting.Proxies.RemotingProxy.EndInvokeHelper (Message reqMsg,
 Boolean bProxyCase)
   at System.Runtime.Remoting.Proxies.RemotingProxy.Invoke(Object NotUsed, MessageData&
 msgData)
   at ThreadExceptions.WorkerDelegate.EndInvoke(IAsyncResult result)
   at ThreadExceptions.ExceptionHandlers.EndInvokeCallback(IAsyncResult iar) in
 asynccallback.cs:line 23

This output looks remarkably similar to the output associated with Listing 15.23. This is because the same mechanisms are involved. If an asynchronous callback is expected to throw an exception, then be sure to wrap the call to EndInvoke with try/catch or the exception will silently disappear.

Thread Exceptions

This sample is largely a repeat of the previous sample with the difference being that a Thread is explicitly started to do the work that throws an exception. This sample illustrates the point that the UnhandledException event is indeed an AppDomain wide property. The exception raised in this Thread is not caught anywhere, so it ends up with the UnhandledException event. The sample that is partially listed in Listing 15.26 illustrates the use of the UnhandledException event. The full source for this sample is available at ThreadExceptionsThread.

Listing 15.26. Using UnhandledException
static void ExceptionFilter(object o, UnhandledExceptionEventArgs e)
{
    Console.WriteLine("Inside ExceptionFilter on thread: {0}  IsTerminating: {1} ",
        AppDomain.GetCurrentThreadId(), e.IsTerminating);
}
static void ThreadStartCallback()
{
    Console.WriteLine("Inside ThreadStartCallback on thread {0} ", AppDomain
.GetCurrentThreadId());
    int result = WorkerThatThrowsException();
    Console.WriteLine("Inside ThreadStartCallback the answer is {0} ", result);
}
static int WorkerThatThrowsException()
{
    Console.WriteLine("Inside WorkerThatThrowsException on thread {0} ", AppDomain
.GetCurrentThreadId());
    throw new ArgumentException();
    Console.WriteLine("Inside WorkerThatThrowsException about to return the answer");
    return 0;
}

. . .
AppDomain.CurrentDomain.UnhandledException += new
        UnhandledExceptionEventHandler(ExceptionFilter);

WorkerDelegate wd = new WorkerDelegate(WorkerThatThrowsException);
ThreadStart ts = new ThreadStart(ThreadStartCallback);
Thread t = new Thread(ts);
t.Start();

This sample first registers a callback in with the UnhandledException event of the current AppDomain. Next, a delegate is created and the Thread that will be doing the work is created and started. The only work that is done is to throw an ArgumentException in WorkerThatThrowsException. Because no exception handlers are available, the callback for unhandled exceptions is called. The output for this sample looks like this:

Inside Main on thread 1716
Inside ThreadStartCallback on thread 336
Inside WorkerThatThrowsException on thread 336
Inside ExceptionFilter on thread: 336 IsTerminating: False

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.116.159