Cleaning up code littered with InvokeRequired [duplicate]
Well how about this:
public static class ControlHelpers
{
public static void InvokeIfRequired<T>(this T control, Action<T> action) where T : ISynchronizeInvoke
{
if (control.InvokeRequired)
{
control.Invoke(new Action(() => action(control)), null);
}
else
{
action(control);
}
}
}
Use it like this:
private void UpdateSummary(string text)
{
summary.InvokeIfRequired(s => { s.Text = text });
}
Calling Invoke
from the UI thread is somewhat inefficient.
Instead, you can create an InvokeIfNeeded
extension method that takes an Action
parameter. (this would also allow you to remove new Action(...)
from the callsite)
I've been reading about the arguments back and forth on adding a logic check to find out if the invoke should be used IFF when not on the UI thread and not on the UI thread itself. I wrote a class that examines the time to execute (via Stopwatch) of various methods to get a rough estimate of the efficiency of one method over another.
The results may be surprising to some of you (these tests were run via the Form.Shown event):
// notice that we are updating the form's title bar 10,000 times
// directly on the UI thread
TimedAction.Go
(
"Direct on UI Thread",
() =>
{
for (int i = 0; i < 10000; i++)
{
this.Text = "1234567890";
}
}
);
// notice that we are invoking the update of the title bar
// (UI thread -> [invoke] -> UI thread)
TimedAction.Go
(
"Invoke on UI Thread",
() =>
{
this.Invoke
(
new Action
(
() =>
{
for (int i = 0; i < 10000; i++)
{
this.Text = "1234567890";
}
}
)
);
}
);
// the following is invoking each UPDATE on the UI thread from the UI thread
// (10,000 invokes)
TimedAction.Go
(
"Separate Invoke on UI Thread",
() =>
{
for (int i = 0; i < 10000; i++)
{
this.Invoke
(
new Action
(
() =>
{
this.Text = "1234567890";
}
)
);
}
}
);
Results are as follows:
- TimedAction::Go()+0 - Debug: [DEBUG] Stopwatch [Direct on UI Thread]: 300ms
- TimedAction::Go()+0 - Debug: [DEBUG] Stopwatch [Invoke on UI Thread]: 299ms
- TimedAction::Go()+0 - Debug: [DEBUG] Stopwatch [Separate Invoke on UI Thread]: 649ms
My conclusion is that you can safely invoke at any time, regardless whether you are on the UI thread or a worker thread, without significant overhead of looping back via the message pump. However, performing most of the work ON the UI thread instead of making many calls to the UI thread (via Invoke()) is advantageous and improve efficiency greatly.
I realize that there's already one answer that's pretty much spot on, but I wanted to also post my take on it (which I also posted here).
Mine is a little different in that it can slightly more safely handle null controls and can return results when necessary. Both of these have come in handy for me when trying to Invoke showing a MessageBox on a parent form that might be null, and returning the DialogResult of showing that MessageBox.
using System;
using System.Windows.Forms;
/// <summary>
/// Extension methods acting on Control objects.
/// </summary>
internal static class ControlExtensionMethods
{
/// <summary>
/// Invokes the given action on the given control's UI thread, if invocation is needed.
/// </summary>
/// <param name="control">Control on whose UI thread to possibly invoke.</param>
/// <param name="action">Action to be invoked on the given control.</param>
public static void MaybeInvoke(this Control control, Action action)
{
if (control != null && control.InvokeRequired)
{
control.Invoke(action);
}
else
{
action();
}
}
/// <summary>
/// Maybe Invoke a Func that returns a value.
/// </summary>
/// <typeparam name="T">Return type of func.</typeparam>
/// <param name="control">Control on which to maybe invoke.</param>
/// <param name="func">Function returning a value, to invoke.</param>
/// <returns>The result of the call to func.</returns>
public static T MaybeInvoke<T>(this Control control, Func<T> func)
{
if (control != null && control.InvokeRequired)
{
return (T)(control.Invoke(func));
}
else
{
return func();
}
}
}
Usage:
myForm.MaybeInvoke(() => this.Text = "Hello world");
// Sometimes the control might be null, but that's okay.
var dialogResult = this.Parent.MaybeInvoke(() => MessageBox.Show(this, "Yes or no?", "Choice", MessageBoxButtons.YesNo));
My preferred approach for view-only controls is to have all of the control state encapsulated in a class which can be updated without ever going through any inconsistent states (a simple way to do this is to put all things that need to be updated together into an immutable class, and create a new instance of the class whenever an update is needed). Then have a method which will Interlocked.Exchange an updateNeeded flag and, if there isn't an update pending but IsHandleCreated is true, then BeginInvoke the update procedure. The update procedure should clear the updateNeeded flag as the first thing it does, before doing any updates (if someone tries to update the control at that point, another request will be BeginInvoked). Note that you must be prepared to catch and swallow an exception (I think IllegalOperation) if the control gets disposed just as you're preparing to update it.
Incidentally, if a control hasn't yet been joined to a thread (by being added to a visible window, or having the window it's on become visible), it's legal to update it directly but not legal to use BeginInvoke or Invoke on it.