Wednesday, June 30, 2010

Control.Invoke and exceptions - part 2

This post is the second and last of a small series about Control.Invoke and its use when exceptions are thrown and handled. In the previous post we have seen that an exception loses its original stack trace and if you handle it in the background thread you cannot see any reference to the location where the exception was originally thrown.

Here is the same stack trace shown in the previous post:

at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.Invoke(Delegate method)
at TestForm.DoTheWork(Object sender) in C:\TestInvoke\Program.cs:line 36

Why does this happen? The stack trace only shows methods that run in the background thread... Well, this makes sense, because the exception is travelling through two stacks of calls: the first was the main thread, and the implementation of the Invoke() method rethrows the same exception so its original StackTrace property is cleared and only the stack of calls made in the background thread remains.

But the exception type remains the same, so why bother? Is the original stack trace so important? Well, in this case, not particularly... there's only one point in the application where a DivideByZeroException could be thrown. But what if our application and the call to the invoked method were more complex? What if the exception was a NullReferenceException? That stack trace information would be of much value, so let's try to figure out a way to preserve it. If we wrap the DivideByZeroException in another exception, in the method invoked, the stack trace of the InnerException (our DivideByZeroException) should be preserved. Let's give it a try: here is the example of the previous post, modified in the ThrowWithNiceStackTrace() method (the invoked method) so that the original exception is wrapped in a new ApplicationException:

using System;
using System.Windows.Forms;
using System.Threading;

static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new TestForm());
}
}

public class TestForm : Form {
private Button btn;
public TestForm() {
// Just add a simple button with a Click handler
btn = new Button();
btn.Text = "Click me";
btn.Click += new EventHandler(btn_Click);
this.Controls.Add(btn);
}

void btn_Click(object sender, EventArgs e) {
// We are creating a WorkItem to be run
// on a thread from the thread pool
WaitCallback wc = new WaitCallback(this.DoTheWork);
ThreadPool.QueueUserWorkItem(wc);
}

void DoTheWork(object sender) {
// This code is run in a background thread
try {
// We are using Invoke so the call
// to ThrowWithNiceStackTrace() runs in the main thread
this.Invoke(new MethodInvoker(
this.ThrowWithNiceStackTrace));
}
catch (Exception ex) {
Type ext = ex.GetType();
string stackTrace = ex.StackTrace;
}
}
private void ThrowWithNiceStackTrace() {
// This code is run in the main thread;
// calling Control methods is legal
this.btn.Text = "Clicked";
try {
this.ThrowWithLongStackTrace();
}
catch (Exception ex) {
throw new ApplicationException(
"Exception caught in invoked method", ex);
}
}
private void ThrowWithLongStackTrace() {
this.ThrowDivByZero();
}
private void ThrowDivByZero() {
int i = 1 / string.Empty.Length;
}
}

Now we run the sample and in the breakpoint inside the DoTheWork() method we see... the same result??

Yes, the exception thrown by Invoke() is still the DivideByZeroException. Not what you might expect, right? And obviously our precious stack trace information is lost again.

It turns out that Invoke() always throws the innermost exception. Digging through the System.Windows.Forms.dll assembly we can find that a call to Exception.GetBaseException() is made to retrieve the exception that will be rethrown. So not only the stack trace is lost, but also other exceptions we might have thrown to enclose the original exception and add context to it. How bad... this seems to be a bug in the Framework, and googling around we can find that we're not alone, and that at least there is some sort of confirmation of this being a bug in the Invoke method (see here, here and here).

What should we do, if we were interested in that information? Should we implement our Invoke strategy?

Hopefully not... the GetBaseException() method is defined virtual: let's try to fool this behavior, overriding the GetBaseException() method:

using System;
using System.Windows.Forms;
using System.Threading;

static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new TestForm());
}
}

public class TestForm : Form {
private Button btn;
public TestForm() {
// Just add a simple button with a Click handler
btn = new Button();
btn.Text = "Click me";
btn.Click += new EventHandler(btn_Click);
this.Controls.Add(btn);
}

void btn_Click(object sender, EventArgs e) {
// We are creating a WorkItem to be run
// on a thread from the thread pool
WaitCallback wc = new WaitCallback(this.DoTheWork);
ThreadPool.QueueUserWorkItem(wc);
}

void DoTheWork(object sender) {
// This code is run in a background thread
try {
// We are using Invoke so the call
// to ThrowWithNiceStackTrace() runs in the main thread
this.Invoke(new MethodInvoker(
this.ThrowWithNiceStackTrace));
}
catch (Exception ex) {
Type ext = ex.GetType();
string stackTrace = ex.StackTrace;
}
}
private void ThrowWithNiceStackTrace() {
// This code is run in the main thread;
// calling Control methods is legal
this.btn.Text = "Clicked";
try {
this.ThrowWithLongStackTrace();
}
catch (Exception ex) {
throw new InvokeException(
"Exception caught in invoked method", ex);
}
}
private void ThrowWithLongStackTrace() {
this.ThrowDivByZero();
}
private void ThrowDivByZero() {
int i = 1 / string.Empty.Length;
}
}
public class InvokeException : Exception {
public InvokeException(string message, Exception innerException)
: base(message, innerException) { }
   public override Exception GetBaseException() {
// Yes, we are lying: the doc says we should return
// 'The first exception thrown in a chain of exceptions'
return this;
}
}
When running this version of the program, if we stop at our breakpoint inside the catch clause of DoTheWork() we find that the exception caught is of type InvokeException, with this stack trace:

at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.Invoke(Delegate method)
at TestForm.DoTheWork(Object sender) in C:\TestInvoke\Program.cs:line 36

But now there is an InnerException of type DivideByZeroException, with the expected stack trace:

at TestForm.ThrowDivByZero() in C:\TestInvoke\Program.cs:line 60
at TestForm.ThrowWithLongStackTrace() in C:\TestInvoke\Program.cs:line 57
at TestForm.ThrowWithNiceStackTrace() in C:\TestInvoke\Program.cs:line 49

Not an elegant solution: we have to add a try catch in our methods called via Invoke(), at least in the methods where the location and nature of possible exceptions is not obvious. And we have to break the 'contract' of the Exception object: the implementation of GetBaseException of our InvokeException class will deliberately lie to callers. I know that lying is a bad thing (see Marcelo's recent post about this), but if we limit its usage to this workaround, we should be safe. It will also be easy to remove the workaround one day, when the fix to Windows.Forms is available: that day, just remove the InvokeException class and where compilation errors appear, there are the pieces of code to remove.

Less than nothing, anyway. I have used this workaround in error logs that we have in production, and I find it useful.


Control.Invoke and exceptions - part 1

Windows Forms in the .Net Framework are now becoming a bit outdated, but they are still widely used.

A common problem with their use is that long computations or tasks that involve gathering data from the web tend to freeze the user interface if executed in the main thread (more precisely, the thread that created the handle, and where form events are run). This impacts the user experience, so the common solution is to resort to background threads to do the hard work, and free the main thread to keep the application always responsive.

Using background threads used to be a tricky task, in Windows programming. Well, it still is, even if newer releases of the .Net Framework have brought tools such as the BackgroundWorker that frees the developer from handling much of the hassle while leaving him with full control of what's going on.

The most common issues involving multithreading in Windows Forms is that almost any call to all classes derived from Control must be done from the main thread. If you call a method from a different thread, and you are lucky, the IDE at runtime warns you that you are making an illegal call, but such problem may slip undetected into release, and then your code might even work as expected in 99% of the times... in the remaining 1% of the runs though your application will behave erratically, and you won't be able to discover why...

Here is a note in the Msdn documentation that remarks that nearly all methods of Control require to be called from the control's thread.

Note

In addition to the InvokeRequired property, there are four methods on a control that are thread safe:
Invoke
, BeginInvoke, EndInvoke, and CreateGraphics if the handle for the control has already been created. Calling CreateGraphics before the control's handle has been created on a background thread can cause illegal cross thread calls. For all other method calls, you should use one of the invoke methods to marshal the call to the control's thread.

A practical way to call methods in Control and derived classes, from your code that runs in the background, is to call Invoke() passing a delegate to the method you need to be called, and any arguments that you need to pass to the method.

The background thread will wait for the call to complete on the main thread, then your background thread will proceed; any return value of the called method is returned by Invoke(), so it can be used by your code. Even exceptions thrown in the method invoked are rethrown.

Everything seems to work fine, but let's make an example, and take a look at the stack trace of the exception:

using System;
using System.Windows.Forms;
using System.Threading;

static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new TestForm());
}
}

public class TestForm : Form {
private Button btn;
public TestForm() {
// Just add a simple button with a Click handler
btn = new Button();
btn.Text = "Click me";
btn.Click += new EventHandler(btn_Click);
this.Controls.Add(btn);
}

void btn_Click(object sender, EventArgs e) {
// We are creating a WorkItem to be run
// on a thread from the thread pool
WaitCallback wc = new WaitCallback(this.DoTheWork);
ThreadPool.QueueUserWorkItem(wc);
}

void DoTheWork(object sender) {
// This code is run in a background thread
try {
// We are using Invoke so the call
// to ThrowWithNiceStackTrace() runs in the main thread
this.Invoke(new MethodInvoker(
this.ThrowWithNiceStackTrace));
}
catch (Exception ex) {
string stackTrace = ex.StackTrace;
}
}
private void ThrowWithNiceStackTrace() {
// This code is run in the main thread;
// calling Control methods is legal
this.btn.Text = "Clicked";
this.ThrowWithLongStackTrace();
}
private void ThrowWithLongStackTrace() {
this.ThrowDivByZero();
}
private void ThrowDivByZero() {
int i = 1 / string.Empty.Length;
}
}

If you set a breakpoint in the DoTheWork method, inside the catch clause, you can see the stack trace property of our DivideByZeroException caught:

at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.Invoke(Delegate method)
at TestForm.DoTheWork(Object sender) in C:\TestInvoke\Program.cs:line 36

Hmmm... missing something? Wasn't our exception thrown in the ThrowDivByZero() method? There seems to be no trace of this in the stack trace!

But stay tuned, there's more to come...