I know that when manipulating UI controls from any non-UI thread, you must marshal your calls to the UI thread to avoid issues. The general consensus is that you should use
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 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));
I am not convinced that Control.Invoke
is the best choice for updating the UI. I cannot say for sure in your case because I do not know the circumstances under in which UpdateSummary
in called. However, if you are calling it periodically as a mechanism for displaying progress information (which is the impression I get from the code snippet) then there is usually a better option. That option is to have the UI thread poll for the status instead of having the worker thread push it.
The reasons why the polling approach should be considered in this case is because:
Control.Invoke
imposes.So consider creating a System.Windows.Forms.Timer
that periodically checks for the text to be displayed on the Control
instead of initiating the push from the worker thread. Again, without knowing your exact requirements I am not willing to say this definitely the direction you need to go, but in most many cases it is better than the Control.Invoke
option.
Obviously this approach eliminates the necessity of the InvokedRequired
check entirely. Nevermind, the fact that it simplies all other aspects of the UI / worker thread interaction.
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.
It is easier to use BackgroudWorker, if possible, for making the UI responsive and use ReportProgress to update the UI because it runs on the same thread as the UI, hence you will not need InvokeRequired.
I cannot comment yet, hopefully someone will see this and add it to the accepted answer, which otherwise is spot on.
control.Invoke(new Action(() => action(control)));
should read
control.Invoke(new Action(() => action(control)), null);
As written the accepted answer will not compile because ISynchronizeInvoke.Invoke()
does not have an overload with only 1 argument like Control.Invoke()
does.
One other thing is that the usage might be more clear as
summary.InvokeIfRequired(c => { summary.Text = text; });
rather than as written
summary.InvokeIfRequired(c => { textBox.Text = text });