Exceptions in thread pool threads

When .NET 2.0 came out back in January 2006 there was a significant change in how the .NET runtime treats exception in thread pool threads: they terminate the process if they are not handled. There are a couple of exceptions to the rule, the most common one being Thread.Abort() called on the thread.

Until now Mono was not compatible in this regard and instead we behaved like the .NET 1.x runtime, i.e., exceptions in thread pool threads were silently ignored. Finally, as some folks already running Mono from master have noticed, Mono behaves like .NET 2.0. The first release incorporating this change will be Mono 2.8.

Code samples

  1. An exception in a thread pool thread causes this code to terminate the process when executed

         ThreadPool.QueueUserWorkItem ((a) => { throw new Exception ("From the threadpoool"); });
  2. The program is also terminated if the callback of an asynchronous delegate invocation throws.

         void Method ()
         {
              WaitCallback wcb = new WaitCallback ((a) => {
                   Console.WriteLine ("Here");
              });
              wcb.BeginInvoke (wcb, OnCBFinished, null);
              ...
         }

         static void OnCBFinished (object arg)
         {
              throw new Exception ("Whatever");
         }

  3. Surprise! If the BeginInvoke() call does not have a callback, the exception is ignored as in the good old 1.x days

         WaitCallback wcb = new WaitCallback ((a) => { 
              throw new Exception ("From the threadpoool");
         });     
         wcb.BeginInvoke (wcb, nullnull);

    Update: yes, if EndInvoke() is called, the exception will be raised in the caller thread. The surprise is the difference between this case and (2), since EndInvoke() is not called in (2).

Effects of this change to the Mono runtime

We have fixed the classes in Mono that were not handling exceptions thrown in the thread pool. If you find one that we've missed, please, let me know.

If your application uses the thread pool explicitly or through Delegate.BeginInvoke(), make sure you are handling the exceptions that might happen there. Look for occurences of (1) and (2) above, but ignore (3). If there are too many to count, you can start by applying the workaround described later or by adding an unhandled exception handler to the root application domain:

        static int Main ()
        {
                AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
                ...
                return 0;
        }

        static void OnUnhandledException (object sender, UnhandledExceptionEventArgs e)
        {
                if (Thread.CurrentThread.IsThreadPoolThread)
                        return// Ignore exceptions in thread pool threads
                Console.Error.WriteLine (e.ExceptionObject);
                Environment.Exit (1);
        }

Workaround: legacy compatibility

If you are an end user stuck with an old application (or a lazy developer), adding the following legacyUnhandledExceptionPolicy to your application configuration file will make exceptions in the thread pool behave like in .NET 1.x:

     <configuration>
          <runtime>
               <legacyUnhandledExceptionPolicy enabled="1"/>
          </runtime>
     </configuration>



blog comments powered by Disqus
This is a personal web page. Things said here do not represent the position of my employer.