MSDN’s canonical technique for using Control.Invoke is lame

While I’m on the subject of using the Control.Invoke method, I’d like to mention a pet peeve of mine. That is, the technique that everywhere on MSDN is proposed for dealing with using that method.


In particular, according to Microsoft, the preferred technique is to write one method that does two completely different things, depending on the value returned by the Control.InvokeRequired property. If the property is true, then Invoke is called using the same method as the target, and if it’s false, then whatever code was really desired to execute is in fact executed. It looks like this:


void DoSomethingMethod()
{
    if (InvokeRequired)
    {
        Invoke((MethodInvoker)DoSomethingMethod);
    }
    else
    {
        // actually do the "something"
    }
}

Now, I’m not a big fan of methods that do more than one thing in any case. It’s my opinion that a method should do one thing and do it well. There are exceptions to every rule of course, but they are few and far between. More importantly, the above pattern does not rise to the requirements of being such an exception.


To understand why, consider what happens if you call Invoke when InvokeRequired returns false. The designers of .NET could have implemented it to throw an exception if you try to call Invoke when not necessary. But there’s no obvious reason that they should have, nor did they. In fact, the Invoke method will “do the right thing” in that case, and simply invoke the target delegate directly rather than trying to marshall it onto the thread that owns the Control instance.


Note that the Invoke method can’t just always do the marshaling. It has to know whether doing so is necessary, because if it tried to marshall the invocation when it wasn’t necessary, it would wind up stuck waiting on the marshaled invocation to happen, which it never would because it’s waiting using the same thread that’s needed for the invocation.


So Invoke simply invokes the target directly, and to decide to do this it checks the same state your own code would be checking when it looks at the InvokeRequired property.


In other words, by using the MSDN-prescribed pattern, you’re duplicating the exact same effort that already is made by the .NET Framework.


My opinion is that it’s better to not have the redundant code, and to take advantage of the fact that .NET is already doing what MSDN proposes you do: just always call Invoke and let .NET sort it out. Now, you may be thinking “but if I always call Invoke using my own method as the target, won’t that cause an infinite recursion?” Yes, it would, so don’t do that. [:)]


Instead, take advantage of C#’s anonymous methods, wrapping all of your invoked logic inside one and invoke that:


void DoSomethingMethod()
{
    Invoke((MethodInvoker)delegate
    {
        // actually do the "something"
    });
}

Note: I prefer anonymous methods for this situation, but some may prefer a lambda expression instead, and especially if what you’re invoking actually returns some value, it might even be more expressive to use that. A lambda expression is a fine alternative to an anonymous method, and winds up compiled to basically the same thing.


One final thought: don’t judge Microsoft too harshly for promoting their inefficient approach so broadly. It’s unfortunate that new examples continue to be created, and that the newer versions of the documentation haven’t been changed to replace the older examples. But prior to C#/.NET 2.0, using an anonymous method in the invocation just wasn’t an option.


I wasn’t using .NET in those early days, and even then I would not have used the “one method, two behaviors” technique. Instead, I would have broken the functionality into two different methods, one that invokes the other. But the MSDN-promoted technique is less inappropriate in that context; in fact, it was as close as they could come to the benefit that anonymous methods offer of keeping all the functionality in a single method. Since I value that feature of anonymous methods so highly, I can hardly fault them for striving for that goal even when they didn’t have anonymous methods to use. [:)]

7 thoughts on “MSDN’s canonical technique for using Control.Invoke is lame”

  1. The Invoke method cannot be called until the window handle is created. Hence it will fail when initializing the text in a control before displaying the window.

  2. Not sure calling Invoke() when it is not required directly calls the method.

    For example, after closing an MDI child form (closing an MDI child form does not dispose it) if you call Invoke() over the form, the delegate method is not called (a breakpoint set there does not get hit). If Invoke() were called before the MDI child form was closed, the delegate method does get called (breakpoint would get hit).

    There is more going on than directly calling the method when Invoke() is not required.

  3. I am not sure what you’re trying to say.

    MDI child forms behave the same way that regular forms do: if not shown with the ShowDialog() method, then when the form is closed, first the FormClosed event is raised, during which the form has not yet been disposed and calls to Invoke() work normally, and then the Disposed event is raised, as the form is actually disposed, after which time calls to Invoke() would fail as would most other attempts to access the members of the form instance.

    It is not correct that closing an MDI child form does not dispose it, nor is it correct that if you call Invoke() before the form is disposed (in a handler of the FormClosed event, for example) that the invoked delegate is not executed.

    To confirm this, I tested using both .NET 3.5 and 4.0.

    Taken at it’s simplest, it is true that “there is more going on than directly calling the method when Invoke() is not required”, but only in the sense that the Invoke() method does do more than just call the method (a lot more, some of which is the equivalent of checking InvokeRequired).

    I am not aware of any scenario in which one could successfully execute code by checking InvokeRequired and skipping the call to Invoke() when the property returns false, but not by calling Invoke() directly. In particular, if calling Invoke() would fail, it’s because the form has been disposed and the call would throw an exception, in which case whatever code might have ever needed the call to Invoke() in any situation would also throw an exception (because the things in a form that require Invoke() are also the things that don’t work if the form has been disposed).

    If such a scenario does exist, closing an MDI child isn’t it. Calling Invoke() before the form is actually disposed works fine (e.g. in the FormClosed event handler), and attempting to call it after simply fails as one would expect (an InvalidOperationException is thrown).

  4. Sorry, I wrote that comment off in a hurry late last night. I will try to be clearer with stating my observations.

    However, at a basic level, we seem to be in agreement that there is more going on than a direct call to the delegate method when Invoke() is called from the UI thread that owns the control.

    I would also like to add that my observations were made with the .NET 2.0 framework.

    MDI child windows don’t behave the same way as normal forms when it comes to closing them (for the purpose of this discussion, I am not considering dialog windows, i.e. ShowDialog()). As can be seen from the documentation here (http://msdn.microsoft.com/en-us/library/system.windows.forms.form.close.aspx) closing a normal form ends up disposing it as well, whereas closing an MDI child form does not dispose it directly always.

    When I call Invoke() from a background thread and the target is an MDI child window (i.e. Invoke() is called over an MDI child form) and the MDI child window has not been closed yet, the delegate call is marshaled into the UI thread of the MDI child window (as should be).

    If the MDI child form is already closed by the time Invoke() is called from the background thread, the invocation fails with InvalidOperationException (as should be).

    When Invoke() is called from the UI thread of the MDI child form itself (i.e. in a situation where Invoke() is not required), the invocation behaves like a direct call to the delegate (again, as should be).

    However, when Invoke() is called from the UI thread of the MDI child form and the MDI child form has already been closed, the delegate method does NOT fire and an InvalidOperationException is NOT thrown. This is a result that I was not expecting – I was thinking (fairly reasonable thought, I think) that Invoke() would directly call the delegate method in this case. This is what made me conclude that there is more going on with Invoke() than just a direct call to the delegate method when Invoke() is not required.

    Hope that states my observations more clearly.

    Any thoughts on this?

  5. I have not seen the behavior you describe. It’s possible that the Control.Invoke() method had a bug that was fixed between .NET 2.0 (the version in which you say that happens) and .NET 3.5 SP1 (the version in which I checked).

    And to be clear: such behavior would in fact be a bug. Control.Invoke() should not fail silently. It either should invoke the delegate, or it should throw an exception. Failing to invoke the delegate but not throwing an exception would be unacceptable behavior.

    Note that the situation described in the Form.Close() method docs is when the MDI child has already been hidden and you call Close(). So I suppose it’s possible that the bug was intentional in a previous version of .NET. But in the versions I’m testing with, even closing the MDI child when it’s been hidden first doesn’t change the behavior: the form is still disposed, and attempting to invoke a delegate after the form has been disposed causes an exception to be thrown.

    If you have a concise code example that demonstrates the behavior you’re describing, especially if you can confirm that the behavior still exists in .NET 3.5 SP1 or later, please feel free to post it here.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>