Control.Trifecta: InvokeRequired, IsHandleCreated, and IsDisposed
Talking about Control.InvokeRequired seems like something so old, yet as I do a Google blog search for InvokeRequired I see only partial explanations and haphazard examples. I guess that’s what happens in “sample code world”, but the problem is that too many developers live in that world. (I know I’ve lived there before, and sometimes I still visit.) Just read The Daily WTF for some great examples, and before you ask–no, none of them are me. That I know of.
If you don’t know what InvokeRequired is, or why you want to use it, you should definitely read the MSDN docs to get up to speed. In fact, much of what I want to say is available on that page. It’s also accompanied by a fairly simple-but-complete multithreading WinForms example.
Most examples show something like this:
1 private void UpdateTheUI() 2 { 3 if (this.InvokeRequired) 4 { 5 this.BeginInvoke(new MethodInvoker(UpdateTheUI)); 6 } 7 else 8 { 9 // Update the control, etc. 10 } 11 }
That’s all fine and good, but there are a few scenarios that can affect this code. Some of them can be mitigated by your design. For example, did you know that Control.InvokeRequired can return false even when you’re calling it from a different thread? Yep, it’s true! From the lips of MSDN:
This means that InvokeRequired can return false if Invoke is not required (the call occurs on the same thread), or if the control was created on a different thread but the control’s handle has not yet been created.
A common scenario where this might occur is if a background process is started from the control’s constructor (typically considered a no-no, but sometimes it might be necessary). If the background work completes quicker than expected you could be trying to update a UI that hasn’t even been shown yet. The solution in this case is to check the IsHandleCreated property if InvokeRequired is false, and defer the update until the control’s handle has been created. (Save yourself some time: don’t check the Handle property for null or IntPtr.Zero–referencing the property at all will create a handle on the spot–and likely on the wrong thread, too.)
There’s one more gotcha I want to mention. After background processing has elapsed, it’s entirely possible your control has since been disposed. It’s always important to check IsDisposed before updating your UI. A common problem we ran into at work (one which I took part in) was that we threw in a check for IsDisposed at the top of the method and return if it was true, like this:
1 private void UpdateTheUI() 2 { 3 if (this.IsDisposed) return; 4 5 if (this.InvokeRequired) 6 { 7 this.BeginInvoke(new MethodInvoker(UpdateTheUI)); 8 } 9 else 10 { 11 // Update the control, etc. 12 } 13 }
Unfortunately we forgot that besides the InvokeRequired property the only thread-safe methods on the Control class are: Invoke, BeginInvoke, EndInvoke, and CreateGraphics (all assuming the handle has been created, of course). Notice that IsDisposed isn’t in that list?
Here’s an example that (to my current knowledge) addresses all of these potential issues, at least in one way or another. At this point you can call UpdateTheUI from almost any context and it won’t bomb. That’s not a promise that it will do exactly what you hope it will do (i.e. update the UI) but that’s where your control’s design comes in.
1 private void UpdateTheUI() 2 { 3 if (this.InvokeRequired) 4 { 5 this.BeginInvoke(new MethodInvoker(UpdateTheUI)); 6 } 7 else 8 { 9 if (this.IsDisposed) return; 10 11 if (!this.IsHandleCreated) 12 { 13 // Background processing completed before the 14 // control's handle has been created. Do whatever it takes 15 // to indicate that a deferred update must occur (after the handle has 16 // been created). 17 18 _deferUIUpdate = true; 19 return; 20 } 21 22 // Update the control, etc. 23 } 24 }
January 8th, 2008 at 11:34 pm
[...] So that’s great, and even better it’s easy. When your application starts, you can load culture/language information from whatever persistence mechanism you prefer, and essentially "set it and forget it", since all Controls should be created on the same thread. [...]
November 15th, 2008 at 12:15 pm
Hello
Your Article is very interesting
But I’m still stuck trying to use Invoke
If I run in debug mode everything is fine
- in release it hangs after a random number of steps
December 19th, 2008 at 11:49 pm
The good resource is informative and actual
December 22nd, 2008 at 4:49 pm
[...] talked before about System.Threading.SynchronizationContext, as well as BeginInvoke/InvokedRequired/IsHandleCreated. In a multi-threaded Windows Forms application they can easily be mis-used, introducing difficult [...]
March 9th, 2009 at 9:50 pm
If the object becomes disposed on another thread just before line 5 executes, you’ll get an InvalidOperationException. This can happen, for example, if the user closes the window just as the background work completes.
May 1st, 2009 at 10:29 am
If unmanaged counterpart of a control is not yet created, then InvokeCreated returns false. This places IsDisposed call on any ol’ thread. As I understand, IsDisposed should only be executed on Control’s thread. Please review.