Automating the InvokeRequired code pattern

Tom Corelis picture Tom Corelis · Mar 3, 2010 · Viewed 144.9k times · Source

I have become painfully aware of just how often one needs to write the following code pattern in event-driven GUI code, where

private void DoGUISwitch() {
    // cruisin for a bruisin' through exception city
    object1.Visible = true;
    object2.Visible = false;
}

becomes:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

This is an awkward pattern in C#, both to remember, and to type. Has anyone come up with some sort of shortcut or construct that automates this to a degree? It'd be cool if there was a way to attach a function to objects that does this check without having to go through all this extra work, like a object1.InvokeIfNecessary.visible = true type shortcut.

Previous answers have discussed the impracticality of just calling Invoke() every time, and even then the Invoke() syntax is both inefficient and still awkward to deal with.

So, has anyone figured out any shortcuts?

Answer

Olivier Jacot-Descombes picture Olivier Jacot-Descombes · Aug 29, 2012

Lee's approach can be simplified further

public static void InvokeIfRequired(this Control control, MethodInvoker action)
{
    // See Update 2 for edits Mike de Klerk suggests to insert here.

    if (control.InvokeRequired) {
        control.Invoke(action);
    } else {
        action();
    }
}

And can be called like this

richEditControl1.InvokeIfRequired(() =>
{
    // Do anything you want with the control here
    richEditControl1.RtfText = value;
    RtfHelpers.AddMissingStyles(richEditControl1);
});

There is no need to pass the control as parameter to the delegate. C# automatically creates a closure.


UPDATE:

According to several other posters Control can be generalized as ISynchronizeInvoke:

public static void InvokeIfRequired(this ISynchronizeInvoke obj,
                                         MethodInvoker action)
{
    if (obj.InvokeRequired) {
        var args = new object[0];
        obj.Invoke(action, args);
    } else {
        action();
    }
}

DonBoitnott pointed out that unlike Control the ISynchronizeInvoke interface requires an object array for the Invoke method as parameter list for the action.


UPDATE 2

Edits suggested by Mike de Klerk (see comment in 1st code snippet for insert point):

// When the form, thus the control, isn't visible yet, InvokeRequired  returns false,
// resulting still in a cross-thread exception.
while (!control.Visible)
{
    System.Threading.Thread.Sleep(50);
}

See ToolmakerSteve's comment below for concerns about this suggestion.