How will you make it if you never even try?

April 26, 2005

Control.Invoke() and exception propogation (short form)

I found out some interesting things about Control.Invoke() with regards to exception propogation. This entry is the short form summary of the findings (the long form, which also talks about the symptoms that alerted us about our problem, is here).
Overview of what happens during a Control.Invoke() call
1. Checks are made to determine whether you are already on the thread you need to be on. For the sake of this discussion, I am assuming that you are *not* on the desired thread, and that you therefore really did need to call Control.Invoke (IsInvokeRequired = true).
2. .NET instantiates an instance of type Control.ThreadMethodEntry. This object instance (hereafter referred to as “the Entry Variable”). will simply contain the information needed to call your delegate, and any other information that needs to be “passed over” to the destination thread.
3. When constructing the Entry Variable, .net passes the method you want called, the array of arguments for your method, and the call stack of the originating thread.
4. All those constructor arguments essentially get stored into the state of the Entry Variable instance.
5. .NET then uses the Win32 API call “RegisterWindowMessage” from user32.dll to register a message that can be sent to a Windows OS window.
6. .NET then uses the Win32 API call “PostMessage” to post the message previously registered to the Control’s window.
7. The code for Control.Invoke then blocks by calling WaitOne() on a ManualResetEvent that is a field in the Entry Variable.
8. While the originating thread is blocked, the message pump of the Control you’re targeting continues (as always) to process messages. At some point, it receives the message which was posted in step 6. In Control.WndProc, one of the checks applied to each incoming message is whether its message id is the one that has been used specifically for the purpose of Control.Invoke (the id originally returned from the RegisterWindowMessage call was stored in a field, and is now used to compare against the incoming message).
9. If the message id matches, then you’re now on the destination thread, and the “Entry Variable” (which is part of the state of the Control object) contains the information you need to execute the desired call.
10. The system takes the call stack which is stored in a field in the Entry Variable (the call stack of the originating thread), and then sets the destination thread’s call stack to the same thing. This sets the stage for calling your operation, because the call stack will be such that it contains all the calls from the originating thread that led to your Invoke, *and* all the calls that result from preforming your operation. Note that this means that any exception that arises from your operation can now propogate back to code that was originally called on the originating thread. More on this later in the algorithm.
11. The system calls InvokeMarshalledCallbacks(). This simply does a stardard delegate invocation to execute the operation you specified.
12. The delegate invocation is called inside a try/catch block. The handler catches System.Exception (everything pretty much), and simply takes the exception value and stores it in a field in the Entry Variable. The exception is *not* rethrown, which means it is swallowed.
13. The system then restores the destination thread’s original call stack (to the value it had before it was intentionally overwritten in step 10).
14. The system then calls the Complete() method on the Entry Variable. This results in the ManualResetEvent being signalled, which unblocks the originating thread.
15. The code on the destination thread is now complete. It simply resumes its message pump.
16. Since the ManualResetEvent was signalled, the originating thread is now no longer blocked. Control flow resumes immediately after the WaitOne() call.
17. The originating thread looks into the Entry Variable and sees that its “exception1” field (as named by Reflector) is not null. Therefore, it knows an exception was thrown.
18. NOTE: The call stack associated with this exception has been “tampered with” by the intentional steps listed above which overwrote the threads’ call stacks before invoking your operaton. The call stack for the exception is thus a “splice” of 2 separate call stacks. The top half of the call stack will include all calls that happened on the originating thread from the thread’s birth to the point where you called Control.Invoke. The bottom half of the call stack will include everything that happened on the destination thread, starting at the point that your operation was actually invoked and ending at the point the exception was thrown. The exception naturally propogates back up the call stack. It has already traversed the “bottom half” of the call stack, at which point it was caught, placed in a field, and swallowed. Now, it has been discovered in code that is executing on the originating thread.
19. At this point, the originating thread rethrows the exception. Therefore, it continues propogating back up its call stack, moving backwards from the present spot through all of the calls that led to this point, going all the way to the thread’s birth if not dealt with sooner than that.
In a Nutshell
An exception thrown by your operation that you called via Control.Invoke() will propogate as expected up the destination thread, until it reaches your Control.Invoke(). Then, the exception itself is “marshalled back” to the originating thread. On the originating thread, it will continue to propogate back up all the way to the birth of that thread if allowed.
Differences with Control.BeginInvoke()
The same .NET code handles Control.BeginInvoke() calls. However, there is a flag called isSynchronous which will be false if you called BeginInvoke. As a result of the flag being false, .NET will not monkey with the call stacks of your threads, and it will not call WaitOne() in the originating thread. Finally, if your operation throws an exception, .NET does *not* marshall that exception back to the originating thread. Instead, .NET calls Application.OnThreadException() on the destination thread, which will cause your exception to appear as an unhandled thread exception on the destination thread.

Advertisements

April 12, 2005

Control.Invoke() and exception propogation (long version)

Note: This is the “long version” of something I found about Control.Invoke() and exception propogation. Click here for the “short version”.
Just found out something interesting while working on my Home Depot project regarding how exceptions are propogated when using Control.Invoke(). Thought I’d share it.
If you don’t know what Control.Invoke is…
here’s a very brief intro. When you create Windows forms apps, you are using .NET controls such as Button and TextBox. All .NET controls are written in such a manner that they are only safe to access from one thread — the thread which created them. If you’re doing something from a different thread and you want to make something happen to a control (such as change its appearance), then you need to use Control.Invoke(). It takes a delegate, and causes the thread which created the control to call your method. Look here for a more comprehensive discussion, or check out Chris Sells’ book (among others).
We were seeing very WIERD symptoms…
We have a Windows form app with one main UI thread (henceforth called the “main thread”) and one worker thread which communicates with a scanner device attached to an RS232 port (henceforth called the “Scanner thread”). When a scan is received on the scanner thread, it needs to call a method on a control, so the current screen can respond to the “scan event”. However, we need that method to execute on the main thread, so that the scanner can go back to looking for and processing the next scan. Therefore, we use Control.Invoke to marshall the call of the notification method over to the main thread.
In the course of diagnosing some bugs reported by QA, I noticed that the code I was stepping through would simply stop executing! In other words, I would be stepping through in the debugger, say on line 5 of a method with 18 lines in it, and suddenly, it would cease executing that code! It would never execute lines 6-18. Yet, the application would appear responsive — the user can click to open drop downs, resize windows, and click buttons!
Here’s what we found…
After doubting my sanity for a while, I decided to put a try / catch around the last statement that had been executed. The only thing I could think of that would cause execution flow to simply cease was an exception. But, that really didn’t make sense, because we have carefully designed exception handling scheme which includes a top level exception handler that was simply never being reached. Nevertheless, I did place a try/catch around the last statement (statement 5 out of 18 in the method I mentioned above), and sure enough, an exception was occurring.
The exception would have been easy to fix, but the bigger problem now was whatever was causing the exception to be mysteriously swallowed. So, I traced it as it propogated back UP the call stack. Here’s where it got interesting.
What Control.Invoke does with Exceptions
I found that the exception propogated all the way up to our Control.Invoke call. That makes sense. Then, I found that the exception continued to propogate right into the scanner code (an infinite loop that runs on the Scanner Thread). Not only that, but the exception itself was actually marshalled back to the scanner thread. The scanner thread’s loop is based on some code some consultant (in the distant past) found somewhere (perhaps from Microsoft) and plugged in, and the loop is not designed to deal cleanly with any exceptions other than communication errors. As a result, the exception was being swallowed in a way that also caused the scanner to cease to function (the scanner loop was still executing, but it was in a state where it would no longer detect and handle scans).
This was surprising, because we had expected the exception to make its way to the Main Thread’s top level exception handler, just like any other exception thrown on the main thread. Each thread has its own call stack, and exceptions in .NET simply propogate back up the call stack for the thread they occurred on. In this case, therefore, we expected the exception to propogate up to the message pump loop in the main thread, where it would reach our top level exception handler. We never thought it would “cross over” to the scanner thread, and continue up the scanner thread’s call stack.
Using Reflector to Decompile Control.Invoke()
So, I fired up Reflector to see what was going on. Turns out, when you use Control.Invoke(), the .NET framework takes explicit steps to copy the call stack from the originating thread (the scanner thread in this case) over to the destination thread (the main thread in this case). It does this using a framework class called CompressedStack. In .NET 1.1, this class is undocumented (the help system says “This type supports the .NET Framework infrastructure and is not intended to be used directly from your code.”). But interestingly, if you look at the WinFx documentation (aka, Longhorn stuff), you can see documentation for it. Then, since Control.Invoke() is a synchronous call, the originating thread simply blocks (by calling WaitOne()) until the operation has been completed by the destination thread. Over on the destination side, the destination thread performs the operation, catches any exception and stores it in a field, and sets the event that the originating thread is blocking on. Finally, the originating thread (which is now no longer blocked) looks in the field to see if an exception was thrown, and if so, rethrows it on the originating thread. Because of the earlier work that was done to copy the call stack from the originating (scanner) thread, the call stack associated with that exception includes the entire set of calls from the point where the exception had originally been thrown (in the destination thread) to the entry point of the originating thread. So the call stack actually spans calls that were made in 2 separate threads.
Conclusions
So, this explains what I was seeing. When you use Control.Invoke(), any exceptions thrown on the destination thread will indeed propogate all the way back to the Control.Invoke call, at which point the exception will be “marshalled back” to the originating thread so it can continue right up the originating thread’s call stack. This may affect design decisions you make, especially regarding exception handling.
Differences between Control.Invoke() and Control.BeginInvoke() regarding exception handling
Control.BeginInvoke() is implemented by pretty much the same .NET framework code as Control.Invoke(). The only difference is that a flag called isSynchronous will be set to false if you called BeginInvoke. In this case, the framework does not copy the call stack to the destination thread. It also does not block the originating thread by calling WaitOne. Finally, if the destination thread does throw an exception, the framework calls Application.OnThreadException() with that exception — while still on the destination thread — , which would make this exception behave like any other exception on the destination thread … the exception will propogate up to the message pump (unless of course you handled it earlier), and will never be marshalled back to the originating thread. In our case, BeginInvoke turned out to be a better fit than Control.Invoke.
Note that Control.BeginInvoke() is a completely different animal than calling BeginInvoke() on a delegate. Calling BeginInvoke() on a delegate causes your job to be executed on a thread from the .NET thread pool. Therefore, it introduces an additional thread to the picture. But Control.BeginInvoke does NOT introduce an additional thread … it simply marshalls a call over to the control’s thread without waiting for the results and without allowing the exception to propogate back to the originating thread.

Blog at WordPress.com.