Illegal Cross Thread Exceptions under the debugger when using Windows Forms and Whidbey...

The Windows Forms team gets an A today for some super creativity... When you are running under a debugger, and only under the debugger, you start to get a bunch of really strange cross thread exceptions if you try to do something like call Focus() on your control from a thread not it's own. You have to dig pretty deep to get this information, and since it is likely to change, I'm not thinking of adding it to my existing document on the subject (WinForms UI Thread Invokes: An In-Depth Review of Invoke/BeginInvoke/InvokeRequred). If you think I should then let me know and I might be able to put it at the bottom as an adendum.

Everything starts with a new set of static vars on the Control class. Specifically checkForIllegalCrossThreadCalls, which defaults to Debugger.IsAttached, and inCrossThreadSafeCall which happens to be a ThreadStatic. This second var only gets set if you create an instance of MultithreadedSafeCallScope. Basically any method that needs to grab the Handle of the control and is capable of doing so from an alternate thread should create this class first. Upon disposal the threadstatic gets reverted. For the Invoke method this looks like the following:

Control.MultithreadedSafeCallScope foo = new Control.MultithreadedSafeCallScope(); // Sets us up to access Handle
try { Control marshaller = FindMarshallingControl(); /* old stuff */ marshaller.MarshaledInvoke( this, method, args, true ); }
finally { foo.Dispose(); }

When you don't create one of these new classes bad things start to happen. Basically accesses to the Handle property of the control will all start to throw. This not only happens on your current thread, but propagates all the way back to the UI thread...

if ( checking && !inSafeCall && invokeRequired ) {
    string exceptionText = ...;
    BeginInvoke( ..., ... ); // Create the UI thread exception
    throw new InvalidOperationException(..., ...); // Kill the current thread most likely
}

You don't have to put up with their crap though. You can turn the entire process off by using the Control.CheckForIllegalCrossThreadCalls property. In fact, in release mode applications you'll probably ALWAYS want to perform this step. You really don't want uncontrolled exceptions being tossed in the event a user accidentally or even purposely attaches a debugger to a process before your application starts to run. You also get a perf gain out of making sure it isn't set because you reduce the number of branch control expressions that have to be run.

On the flip-side if you want these exceptions, don't expect to get them if you attach your debugger late in the game. There isn't any process in place to update the flag as a debugger is attached, only if a debugger is initially attached. If you are debugging and trying to narrow down these exceptions through late attachment, you'll have to manually set the static.

This isn't a drastic change by any means, but it can be confusing to new and existing users of the API. It creates a new behavior or response in various conditions that isn't expected. It'll effectively drop your code and kill your threading. At the same time it does notify you of a condition that shouldn't be in your code and can therefore answer many questions about whether or not you are doing the right thing with cross-thread invocation. We'll have to see how this develops over time and see how different people weigh in as they develop Windows Forms applications on top of C# Express 2005 and other debugger integrated environments.

Published Friday, October 08, 2004 7:05 PM by Justin Rogers

Comments

No Comments

Leave a Comment

(required) 
(required) 
(optional)
(required)