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.
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.24–15.25 illustrate the important aspects of this sample. The full source for this sample is in the ThreadExceptionsAsyncCallback directory.
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.
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.
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.
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
3.144.116.159