A better call to Control.Invoke

Whenever one needs to interact with WinForms UI from within another thread (the one that didn't create the UI controls) one has to rely on the Control.Invoke (or BeginInvoke/EndInvoke pair) method. Otherwise one gets an InvalidOperationException exception with message “Cross-thread operation not valid: Control ‘name here’ accessed from a thread other than the thread it was created on”. This happens because WinForms (and .net framework in general) isn’t thread safe.

Let’s say I want to know how many controls are on the form. I’d do it like this (Form f holds the instance of the Form created in some thread):

public static int GetControlsCount(Form f)
{
if (f.InvokeRequired)
return (int)f.Invoke(new Func<Form, int>(GetControlsCount), f);
else
return f.Controls.Count;
}
...
int count = GetControlsCount(f);

This way the method GetControlsCount is thread safe. That’s fine. But what should I do if I require to access many methods in such a thread safe way (I’ll blog about the reason I need it in a future post)? Creating this kind of wrapper for every different method I need to call would be kind of boring at best and difficult to maintain, don’t you think?

So I created a generic method instead, like this:

public static TRet InvokeSync<TRet>(Control control, Func<TRet> func)
{
if (control.InvokeRequired)
return (TRet)control.Invoke(func);
else
return func();
}
...
int count = InvokeSync<int>(f, () => f.Controls.Count);

This is much better because I don’t need to create a wrapper for each method – instead I merely provide a delegate (of type Func<int> in my case). There are two drawbacks to this approach.

1. It won’t work with different number of parameters nor it will work with methods (= actions) that don’t return a value. The only workaround is to create various similar methods that accepts different argument number and return either a result or no result (void). Here is a bunch of such variations:

public static void InvokeSync(Control control, Action action)
{
if (control.InvokeRequired)
control.Invoke(action);
else
action();
}
public static void InvokeSync<T>(Control control, Action<T> action, T argument)
{
if (control.InvokeRequired)
control.Invoke(action, argument);
else
action(argument);
}
public static TRet InvokeSync<T, TRet>(Control control, Func<T, TRet> func, T argument)
{
if (control.InvokeRequired)
return (TRet)control.Invoke(func, argument);
else
return func(argument);
}
public static TRet InvokeSync<T1, T2, TRet>(Control control, Func<T1, T2, TRet> func, T1 argument1, T2 argument2)
{
if (control.InvokeRequired)
return (TRet)control.Invoke(func, argument1, argument2);
else
return func(argument1, argument2);
}

2. Passing Control parameter (which is required to do the synchronization) is such pre .net 3.5-ish. Instead extensions methods can be used, like this:

public static class Extender
{
public static TRet InvokeSync<TRet>(this Control c, Func<TRet> func)
{
if (c.InvokeRequired)
return (TRet)c.Invoke(func);
else
return func();
}

public static void InvokeSync(this Control c, Action func)
{
if (c.InvokeRequired)
c.Invoke(func);
else
func();
}
}

So the final version of the call to InvokeSync looks even nicer:

int count = f.InvokeSync<int>(() => f.Controls.Count);

That’s it.

InvokeSyncExtender.zip (572.00 bytes)

Comments (7) -

  • Miha Markic

    6/30/2009 4:22:35 AM | Reply

    Hi Igor,

    No, I didn't know about that article until now - thanks for the link.
    However, I don't like the approach very much because:
    - I don't want to modify host code (the original code). That's my specific requirement
    - Doing it that way it always synchroinizes with UI which might be not a best thing to do (what if event listener doesn't require UI synchronization or ever if there is no listener at all)
    Not that mentioned approach is bad or anything (I am sure it is fine for some, but no all, situations) - it doesn't fit my situation.

  • Renaud Bédard

    7/13/2009 8:19:51 PM | Reply

    Nice stuff, I'll definitely be using this instead of Begin/EndInvoke pairs!

  • Back Pain

    11/18/2009 2:35:01 PM | Reply

    I don't like the approach very much because:
    I don't want to modify host code (the original code). That's my specific requirement
    Doing it that way it always synchroinizes with UI which might be not a best thing to do

  • hemorróidas

    11/24/2009 12:51:32 PM | Reply

    It won’t work with different number of parameters nor it will work with methods (= actions) that don’t return a value.

  • Miha Markic

    11/24/2009 8:56:03 PM | Reply

    You have to add overloads for both cases (void return and increased count of parameters). It isn't a big deal though as there aren't that many parameters one usually uses (look at Action<> or Func<> definition).
    Plus one can create a template for code generation that would create as many overloads as one wants.

  • Matthieu fereyre

    2/23/2012 11:17:32 AM | Reply

    Awesome ! Thanks. Works good on compact framework.

Loading