This tip endeavors to teach a simple trick for updating a UI safely from a different thread using the ISynchronizeInvoke interface implemented on controls and forms.
Introduction
Typically, Windows Forms and controls themselves are not thread safe, which can greatly complicate things in a multithreaded application. This article presents an alternative to thread synchronization using marshalling to execute some code invoked from an auxiliary thread on the main UI thread.
Conceptualizing this Mess
Threading is difficult, and very easy to get wrong, and when there are synchronization issues, they are murder to track down. If at all reasonable, generally we should avoid multithreading wherever possible if we want robust code.
A very common case where multithreading can complicate things is updating the user interface, which typically must be done from the UI thread since Winforms are not thread safe.
We're going to avoid the synchronization issue entirely by marshalling some code from the calling thread to the main UI thread, causing the code itself to execute on the UI thread. Since everything UI related is then being done on the UI thread, there are no synchronization issues.
Enter ISynchronizeInvoke
. This little interface is a contract that says "hey, I can accept delegates and execute them on this thread" where "this thread" is the thread on which the implementation of ISynchronizeInvoke
was created. Since every form and every control implements this interface, every control can marshal delegate code to its thread (the UI thread). This is extremely useful as we'll see.
Coding this Mess
The code is pretty basic once you get the hang of it.
ISynchronizeInvoke
implements four significant members:
Invoke()
(both overloads) invokes the code in the given delegate on the thread where this instance was created BeginInvoke()
and EndInvoke()
are the asynchronous versions of the above - BeginInvoke()
, unlike Invoke()
does not block. InvokeRequired
indicates whether or not we must use Invoke()
/BeginInvoke()
to execute safely. If it's false, we don't have to marshal the delegate, we can call it directly. The only thing this is good for is performance. It's not strictly necessary to use it.
Let's look at the source:
var thread = new Thread(() => {
for(var i = 0;i<10001;++i)
{
Action action = () =>
{
Progress.Value = i / 100;
};
if (InvokeRequired)
Invoke(action);
else
action();
}
});
thread.Start();
I've heavily commented this for reference. Basically, what we're doing here is spawning a thread, and we want that thread to update the progress bar as it goes. However, the progress bar is on the UI thread, so it's not safe to invoke from another thread.
In order to get around this, rather than implement thread synchronization, we can simply use Invoke()
as above, which will cause all the code in action
to be executed on the UI thread, using the form's ISynchronizeInvoke.Invoke()
method. Note that we can't use a lambda or anonymous delegate directly with Invoke()
or BeginInvoke()
. That's why we hold it in an Action
delegate name action
. Sure it's a little clunky, but it's a vast improvement over having to implement thread safe calls to the UI.
That's all there is to it - and that's good! The easier the better, especially when it comes to thread. Enjoy!
History
- 29th April, 2020 - Initial submission