Search code examples
c#multithreadingwinformsthread-safetyinvokerequired

Automating the InvokeRequired code pattern


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?


Solution

  • 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.

    If you must return a value, you can use this implementation:

    private static T InvokeIfRequiredReturn<T>(this Control control, Func<T> function)
    {
        if (control.InvokeRequired) {
            return (T)control.Invoke(function);
        } else {
            return function();
        }
    }
    

    UPDATE:

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

    public static void InvokeIfRequired(this ISynchronizeInvoke obj,
                                             MethodInvoker action)
    {
        if (obj.InvokeRequired) {
            obj.Invoke(action, null);
        } 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.

    According to the ISynchronizeInvoke.Invoke(Delegate, Object[]) Method documentation we can pass null if no arguments are needed.


    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 and nawfal's comments below for concerns about this suggestion.