Unhandled exceptions can occur in almost any program. When they happen in a secondary (worker) thread, they can kill the application - or worse, be ignored! Prevent unhandled exceptions in secondary threads by using SafeThread.
Contents
While researching the behavior of unhandled exceptions in .NET 2.0 applications for the CALM project, one of the more surprising and frustrating findings was the way in which unhandled exceptions in secondary threads tend to kill the application or be completely ignored! Unhandled exceptions in secondary threads (threads that you create explicitly) will kill the application, even if you have an unhandled exception event handler (plus the contextual information, i.e., which thread, is lost). Unhandled exceptions in worker (ThreadPool) threads and Timer threads, in particular, are ignored by the Common Language Runtime (CLR). These threads just die, with no unhandled-exception event to trap, no warning to the user, nothing. Obviously, in production applications, this is unacceptable (mis-)behavior!
Thus, the SafeThread class was created. SafeThread wraps a regular CLR Thread, but executes the method delegate inside a try-catch block that emits an event when an unhandled exception occurs. Developers can use this event to clean up after the thread, classify the kind of exceptions, or even launch a new thread. For certain kinds of threaded operations like "heartbeat" operations, this is valuable and necessary for a robust application.
The premise behind SafeThread is fairly simple. Since threads rely on a method delegate for execution, all we need to do is wrap the execution of the delegate in a try-catch block and emit an event if we catch an exception. The base Thread class supports a single-parameter delegate and a no-parameters delegate, so SafeThread mimics these constructors. In addition, SafeThread supports the new dynamic delegates (also known as, anonymous methods) for additional convenience. Finally, to complete the facade, SafeThread implements all of the public methods and properties of the Thread class. SafeThread also provides a ThreadCompleted event to signal when processing is completed.
Clearly, it would be best if SafeThread could inherit from Thread, but this is not possible since the Thread class is sealed (MustInherit in VB.NET terminology). The next-best solution would be for SafeThread to implement a common interface for threads (such as IThread), but alas, .NET does not have this either. So, the best we can do is re-implement the interface and wrap a Thread object. Note that this does present a few issues in that some properties and methods of SafeThread are not valid unless the wrapped Thread already exists, as the wrapped Thread object is not created until the Start method is called.
SafeThread may be used exactly as the CLR Thread class, e.g., using a ThreadStart, with the addition of a ThreadException event and a flag to control whether calling Abort on the thread is reported as an exception or not.
SafeThread thrd = new SafeThread(new ThreadStart(this.threadRunner));
thrd.ShouldReportThreadAbort = true;
thrd.ThreadException +=
new ThreadThrewExceptionHandler(thrd_ThreadException);
thrd.ThreadCompleted +=
new ThreadCompletedHandler(thrd_ThreadCompleted);
thrd.Start();
In addition to ThreadStart and ParameterizedThreadStart, SafeThread also offers a SimpleDelegate option, which can be used with any void no-argument method:
SafeThread thrd = new SafeThread((SimpleDelegate)this.threadRunner));
thrd.ShouldReportThreadAbort = true;
thrd.ThreadException +=
new ThreadThrewExceptionHandler(thrd_ThreadException);
thrd.ThreadCompleted +=
new ThreadCompletedHandler(thrd_ThreadCompleted);
thrd.Start();
SimpleDelegate can also be used with an anonymous method:
SafeThread thrd = new SafeThread((SimpleDelegate)
delegate { this.threadRunner(); });
thrd.ShouldReportThreadAbort = true;
thrd.ThreadException +=
new ThreadThrewExceptionHandler(thrd_ThreadException);
thrd.ThreadCompleted +=
new ThreadCompletedHandler(thrd_ThreadCompleted);
thrd.Start();
The ThreadException handler is passed the SafeThread object that threw the exception, and also the Exception that was thrown:
void thrd_ThreadException(SafeThread thrd, Exception ex)
{
}
The ThreadComleted handler is passed the SafeThread object that completed processing, along with a bool to say whether processing was terminated due to an exception or not, and the offending Exception or null (Nothing in VB.NET terminology) if processing completed successfully.
void thrd_ThreadCompleted(SafeThread thrd, bool hadException, Exception ex)
{
if (hadException)
{
}
else
{
}
}
The SafeThread demo application is both contrived and silly: each SafeThread created periodically pulses the animation of the Start SafeThread button in a circle. Each SafeThread created will pulse the animation 99% of the time, and 1% of the time will throw an unhandled divide-by-zero exception. If the SafeThread survives 100 iterations, it completes successfully and emits a ThreadCompleted event. The ListBox shows what is happening with each SafeThread.
To use the SafeThread demo application, build and/or run it. Click the Start SafeThread button several times - the more SafeThread objects created, the faster the button will move in a circle. Click the Stop SafeThread button to end the SafeThread objects in the order created. Click a ListBox item to see the full item text in a MessageBox. All of the SafeThread objects are ended when the application is closed.
Just for fun, and to address some good points raised by Daniel Grunwald in the comments below, I have added an "unsafe" thread demo application to the article to better illustrate what happens without exception trapping or SafeThread. Click the Start Unsafe Thread button to launch a regular CLR Thread that throws an unhandled divide-by-zero exception, and note that with no protection at all, what you get is the send-a-debug-report-to-Microsoft dialog, and the application dies. Run the demo again, but this time, first check the Use Unhandled Exception Handlers checkbox and then click the Start Unsafe Thread button. Now, we get a message box, courtesy of the AppDomain.UnhandledException event... and then, we get the send-a-debug-report-to-Microsoft dialog, and the application dies.
This illustrates several issues with just using the System.AppDomain.CurrentDomain.UnhandledException event handler to trap exceptions in 'pure' secondary threads (i.e., not worker threads or Timer threads):
- There is not enough contextual information available in the event handler to know which thread caused the problem; the
sender argument is the application, not the thread.
- By the time the unhandled exception reaches the event handler, it is too late to do anything about it - the application is already terminating!
- No matter what you do, the application is going to die.
- Finally - and this is the thing I dislike the most about this mechanism - after your unhandled exception event handler finishes, the dreaded send-a-debug-report-to-Microsoft dialog appears!
SafeThread has a few more properties of interest:
SafeThread has a Name property which is passed to the underlying CLR thread on Start. The default CLR thread Name is SafeThread#XXX where XXX is the HashCode of the CLR Thread object.
SafeThread remembers its start argument in the ThreadStartArg property, when used with a ParameterizedThreadStart.
SafeThread provides a generic Tag object property, which is useful for remembering arbitrary information about the thread.
SafeThread provides a LastException property, which records the last exception captured by the SafeThread.
Note that the behavior of unhandled exceptions in secondary threads changed in .NET 2.0. In .NET 1.1, an unhandled exception in a worker (ThreadPool) thread (but not a Timer thread) would be captured by the AppDomain's UnhandledException event. Microsoft provides a backwards-compatibility option in the application configuration file:
<runtime>
<legacyUnhandledExceptionPolicy enabled="1"/>
</runtime>
See MSDN for details.
A SafeThreadPool would be a logical component to use the SafeThread class, as would a SafeTimer class. These may be covered in future article updates.
- 08-07-2008
- Initial version of article published
- 08-08-2008
UnsafeThread demo and source added, to show what happens without SafeThread
UnsafeThread demo description section added
- Edited the Background section to clarify the different behavior of the secondary thread types
- Edited the Notes section to clarify the purpose of the legacy-compatibility option
- Extra License section removed
- 08-17-2008
SafeThread class updated to emit the ThreadCompleted event
SafeThreadDemo application updated to use ParameterizedThreadStart to pass a time-to-live argument to the threadRunner method, and to hook the ThreadCompleted event and update the text in the ListBox
SafeThreadDemo application corrected to remove completed SafeThreads from the threads collection
| You must Sign In to use this message board. |
|
|
 |
|
 |
As I've just experimented with the unsafe thread's unhandled exception handling,
I've managed to omit the nasty dialog "application has stopped working" dialog by handling AppDomain.CurrentDomain.UnhandledException and placing Process.GetCurrentProcess().Kill(); in the end.
Best regards, Bohdan.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
lol - yes, of course! but is killing the entire application when one thread misbehavees really what you want to do - in all cases?
|
| Sign In·View Thread·PermaLink | 4.00/5 |
|
|
|
 |
|
 |
Ok.
First thing to say — I'm not expressing a negative opinion. I do think that exception handling should be made using SafeThread or equivalent.
The post I wrote was meant to complete the vision. I was to argue, that the nasty dialog will appear in any case: I provided a workaround to prevent it from appearing.
After all, there may be third party libs. Closed source. My information can be useful too.
By the way, I played with the handler and find out that it is possible to go on running even by catching the exception in AppDomain by cost of 1 dead thread (aka "zombie").
To sum up, I value your approach (btw, I use the equivalent snippet)
And I hope my comments bring important info to handling the unhandled 
Do they?
With kind regards, Bohdan (aka modosansreves)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
i didn't mean to imply that your statement was not valuable - but it is sort of off-topic, since the article is talking about exception-traping in secondary threads.
the 'zombie thread' approach is interesting, but it looks like the zombie thread would wake up an hour later and still kill the application! 
and i would be remiss if i didn't include a link to my own answer to the stackoverflow.com question
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi Steven, great Job !
I'm trying to use your class from VB.NET and I see that the method SetApartmentState does not work. The error is here: public void SetApartmentState(ApartmentState state) { if (_thread != null) { _thread.SetApartmentState(state); } else { _aptState = state; } }
because, _thread is not null, and start method is:
public void Start() { _thread = new Thread(new ThreadStart(startTarget)); _thread.Name = this.Name; if (_aptState != null) { _thread.TrySetApartmentState((ApartmentState)_aptState); } _thread.Start(); }
and _aptState = null
So, if you try this modification in SetApartmentState:
public void SetApartmentState(ApartmentState state) { if (_thread != null) { _thread.SetApartmentState(state); } _aptState = state; }
all run fine !
Thanks for your job.
Martin Severgnini http://www.pdutech.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi Steven, congratulations for your excellent article and code. This was exactly what i needed: gracefully terminate a worker thread without ending my main service. I´m building a scheduler, that works with late-binded assemblies (the scheduler tasks) with in turn have dependencies on other assemblies. After all the try-catches (and exception events) implemented, i was still getting unhandledexceptions when some of these assemblies was missing. It was impossible to handle it inside the thread, because the whole code the thread was running, was compromised. Some could say, how do you want your application to still running with missing assemblies?! But as a windows service scheduler, final users may add/remove tasks during runtime, and Murphy never sleeps...
Thanks! Marcello Yesca
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
Steve, I code in VB but I have need for your solution. I used tool on the internet to convert your code to VB, however, I ended up with a memory leak. Can you port your code to VB and make it available? Thanks, Gary
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi Gary!
I'm sorry, I just don't have time available to do that at present.
A simpler and more immediate solution would be to build a class library project containing SafeThread, then just reference the assembly from your VB project.
Good luck!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Steve, Thanks for the quick reply. I will try your suggestion, however I'm not sure it will help me. With your suggestion in minde, I decided to test your code. It turns out that it has a similar memory leak. I'm not familiar enough with C# to even attempt to fix that problem. If I do get it working, I will let you know. Thanks again Gary
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi Gary!
What kind of memory leak? If you want to email me the test code I'll take a look at it
steven DOT lowe AT nov8r.com
thanks!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi Steve, I just stumbled on an article where someone else was having the same problem... This person indicated he only saw the problem when looking at a program that was complied in debug mode. When compiled in release mode, the problem is not there. So I tried it and they are right. Appearently it is a know issue with Microsoft so it is not a problem with your code. Good news huh. Thanks, Gary
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi Gary!
That is good news, especially since neither of us could figure out the cause!
Thanks for clearing this up!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi Gary!
And thank you for the VB version of the SafeThread code/project. I will update the article with this soon.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
AFAIK, garbage collector works differently in debug mode in order to let you track the objects to be disposed.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
I have been developing a distributed program lately using CSLA.Net framework and the exception messages from the application server were just being ignored and i have been looking for a solution for almost a week now..until i came across your superb SafeThread article.
Just wanted to say that you'v done a great job with SafeThread and on the explination ...its very simple.
Thankx
Shaheem. 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
You're welcome, Shaheem, I'm glad you found SafeThread to be useful.
Please remember to vote for the article - and visit my web site!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I add the Condition for bHadException on Catch block. Is it right? protected void startTarget() { Exception exceptn = null; bool bHadException = false; try { bThreadIsAborting = false; if (_threadStart != null) { _threadStart.Invoke(); } else if (_parThreadStart != null) { _parThreadStart.Invoke(_arg); } } catch (Exception ex) { if (ex is ThreadAbortException) bHadException = false; else bHadException = true; exceptn = ex; this._lastException = ex; OnThreadException(ex); } finally { OnThreadCompleted(bHadException, exceptn); } }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
The original intent for hadException in the ThreadCompleted event handler was to easily tell us if the thread had completed successfully, i.e. all the work was completed with no exceptions.
So, the original code always treated thread abort as an exception, and just used the ShouldReportThreadAbort flag on the OnThreadException to decide whether to report it (separately) or not.
Your change is perfectly valid though! This would result in hadException being false instead of true in the ThreadCompleted event handler when the exception was ThreadAbortException. That would mean that if you wanted to be sure that your SafeThread had completed all of its work, you would also have to check for (ex == null) as well as hadException being false.
thanks for reading and commenting!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
My first thought after reading this was, "Doesn't anyone use try/catch any more?". You can use try/catch in your thread code, and they will trigger on exception. At the laziest level, wrap the main entry function of the thread in a try/catch. If you need the thread to continue after an exception has triggered, include a finally block that can do the cleanup and restart work you need.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Of course using try/catch is always better than "emergency backup" measures, but sometimes the thread itself does not know enough to clean up. For example, a "heartbeat" thread keeping a session alive every few minutes would not necessarily have enough information to restart itself. In this case, notifying the code that launched the heartbeat thread may be a better solution.
Thanks for reading and commenting!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Certainly it would seem prudent to have some sort of try-catch block on a thread, but if many different main thread procedures are going to be using similar exception handling it may be better to do the handling in a wrapper than to code it within each thread proc.
Another thing that might be useful to include within the wrapper would be an option to indicate whether, how, and how aggressively a thread should auto-restart. An unconditional immediate auto-restart may be bad, because a fault that would cause it to immediately throw an exception would result in the thread being repeatedly relaunched. Further, in some cases it would be fine to rerun the thread procedure in the same thread, while in other cases it should be restarted in a new thread (to allow threadstatic variables to be reinitialized).
I would think a good approach for dealing with the restart timing might be to have a restart_delay value which doubles every time an exception occurs unless the thread has gone a certain amount of time without an exception. If the restart time gets too large, fire a "things seem broken" event.
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
The following event handlers seem to catch 97%* of the exceptions:
System.AppDomain.CurrentDomain.UnhandledException (catches all exceptions on secondary threads) System.Windows.Forms.Application.ThreadException (catches exceptions on the Windows Forms thread) System.Windows.Threading.Dispatcher.CurrentDispatcher.UnhandledException (catches exceptions in the WPF dispatcher) try{}catch{} in void Main (catches exceptions on the Main thread that crash Windows Forms/WPF)
The missing 3% are fatal exceptions (e.g. stack overflow) and exceptions in Drag'n'Drop events (both in Windows.Forms and WPF) - those get silently swallowed if not caught in the event handler.
Note that I am able to handle AppDomain.UnhandledException fine without the compatibility setting. You still get the event in .NET 2.0, but the exception will cause the process to terminate after the event handler has run. But you can use the event to display a MessageBox/custom error dialog. You could even keep the app (sort of) going on by putting an infinite sleep at the end of the event handler.
* 97%: just a rough guess based on my experiences with unhandled exception in a large Windows.Forms application
modified on Thursday, August 7, 2008 5:28 PM
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
I think you're totally missing the point. This article deals with exceptions in the context of threads. AppDomain.UnhandledException and others don't give you all details about the Thread etc.
GREAT ARTICLE BY THE WAY! 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|