Monday, November 11, 2013

Catching Exceptions from a different thread, Exception Handling in multithreaded environment - C#

Note: Examples are on .NET 3.5 and does not consider .NET 4.0 features like Parallel Task Library.

If there is any exception in a method that is being executed on a different thread, those exception details will not shared with the main thread. It is necessary to catch any exception on a worker\different thread to debug issues especially in multithreaded environment.



// Wrong example: Attempting to catch exception from a thread in caller method
try
{
    Thread t = new Thread(new ThreadStart(LocalMethod));
t.Start(); } catch (Exception ex) { //do something with ex }

Few points from MSDN about "there is no such thing called *Unhandled exception* in Managed code" after .NET version 2.0:


/** Straight from MSDN
The most significant change pertains to managed threads. In the .NET Framework versions 1.0 and 1.1, the common language runtime provides a backstop for unhandled exceptions in the following situations:
  • There is no such thing as an unhandled exception on a thread pool thread. When a task throws an exception that it does not handle, the runtime prints the exception stack trace to the console and then returns the thread to the thread pool.
  • There is no such thing as an unhandled exception on a thread created with the Start method of the Thread class. When code running on such a thread throws an exception that it does not handle, the runtime prints the exception stack trace to the console and then gracefully terminates the thread.
  • There is no such thing as an unhandled exception on the finalizer thread. When a finalizer throws an exception that it does not handle, the runtime prints the exception stack trace to the console and then allows the finalizer thread to resume running finalizers.
The foreground or background status of a managed thread does not affect this behavior.
For unhandled exceptions on threads originating in unmanaged code, the difference is more subtle. The runtime JIT-attach dialog preempts the operating system dialog for managed exceptions or native exceptions on threads that have passed through native code. The process terminates in all cases.
**/

Let us take a look at possible ways of handling exceptions from different threads:



a. Adding a method which in turn invokes the method that executes on a different thread and which might be process heavy will allow to catch the exceptions and execute some code to handle this exception. By handling exceptions this way, application can made sure of running even when there are exceptions on different threads.

This pattern is used widely across different multithreaded applications. This method which allows calling thread method and handling exceptions will receive "method to execute" and "exception handler method" as arguments. It invokes "method to invoke" in try block and call the exception handler in catch block. Below code explains this approach, though it is not real-time example.

static void SafeThreadCaller(Action method, Action handler)
        {
            try
            {
                if (method != null)
                {
                    method();
                }
            }
            catch (Exception e)
            {
                handler(e);
            }
        }

        static void ExceptionHandler(Exception e)
        {            
            Console.WriteLine("Handled at ExceptionHandler in SafeHandler thread: "+ e.Message+". Thread ID: "+ Thread.CurrentThread.ManagedThreadId);
        }

        static void ThreadMethod(values v)
        {
            if (v.a > v.b)
                throw new ArgumentException("Arguments Invalid");
        }

Invoking above SafeThreadCaller method is easy using lamda expressions. It expects two Action delegates. If the thread method expects argument, it is easy to use lamda expressions as substitute for delegate as below:
//Caller method
public static void ThreadExceptions()
        {
            Console.WriteLine("In Main Method Thread ID: " + Thread.CurrentThread.ManagedThreadId);
            Thread t = new Thread(() => SafeThreadCaller(() => ThreadMethod(new values() { a = 5, b = 3 }), ExceptionHandler));
            t.Start();
        }

Note: "values" is a simple class created to hold two values a and b.

b. There is another way where information can be passed from thread method to caller through "out" parameters. I'm not big fan of this approach, since this might raise the need for the caller to wait for other thread to join.

public static void ThreadExcetionsAtCaller()
        {
            Exception ex = null;
            Thread t = new Thread(() => ThreadMethodWithOutParameter(new values() { a = 5, b = 3 }, out ex));
            t.Start();
            Console.WriteLine("In Main Method Thread ID: " + Thread.CurrentThread.ManagedThreadId);
            t.Join();
            if (ex != null)
            {
                Console.WriteLine("Handled at Caller: " + ex.Message + ". Thread ID: " + Thread.CurrentThread.ManagedThreadId);
                File.AppendAllText(@"D:\temp\1.txt","Handled at Caller: " + ex.Message + ". Thread ID: " + Thread.CurrentThread.ManagedThreadId);
            }
        }
static void ThreadMethodWithOutParameter(values v,out Exception ex)
        {
            ex = null;
            try
            {
                ThreadMethod(v);
            }
            catch(Exception e)
            {
                ex = e;
            }
        }
static void ThreadMethod(values v)
        {
            if (v.a > v.b)
                throw new ArgumentException("Arguments Invalid");
        }

c. If you like to handle all thread exceptions at once, AppDomain has support for AppDomain.CurrentDomain.UnhandledException and also ThreadException events. Your application can add a event handler to these events to print necessary information for any exception.

This method might work allows us to graceful exit the application on unhandled exception instead of crashing with "Unhandled exception occurred" message to the user. Little of house keeping pertaining to the current application domain can also be taken care before exiting the application.


//Not a perfect example :)
 AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler
                                                (CurrentDomain_UnhandledException);
     void CurrentDomain_UnhandledException(object o, UnhandledExceptionEventArgs u)
     {
          Console.WriteLine("UNHANDLED EXCEPTION IN THE APPLICATION!");        
     }
Note: UnhandledExceptionEventArgs will not have information about StackTrace. We can get information about whether CLR instance is terminating because of the current unhandled exception.

No comments:

Post a Comment