Click here to Skip to main content
15,882,017 members
Articles / Desktop Programming / Windows Forms
Tip/Trick

Using ISynchronizeInvoke to Painlessly Create Thread Safe User Interfaces

Rate me:
Please Sign up or sign in to vote.
4.14/5 (5 votes)
29 Apr 2020MIT2 min read 13.5K   196   7   20
This tip shows you how to do UI updates without having to worry about locking.
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:

C#
// create a thread. we use this instead of tasks
// or thread pooling simply for demonstration.
// normally, it's not a great idea to spawn
// threads directly like this.
var thread = new Thread(() => { 
    // fill the progress bar, slowly
    for(var i = 0;i<10001;++i)
    {
        // update the progress bar in a thread safe manner
        // you can't use a lambda here without assigning
        // it to a specific delegate type. Here, we use
        // Action since it doesn't need any arguments
        // or a return value
        Action action = () =>
        { 
            // execute this code on the UI thread
            Progress.Value = i / 100; 
        };
        // this marshals the code above onto the thread
        // that this form is running on (the UI thread)
        // everything within the action code is executed
        // on the UI thread. We only do this if 
        // InvokeRequired is true, otherwise we can
        // invoke the delegate directly. In this example,
        // it should always be true, but in the real world,
        // it will not be necessarily. Calling Invoke when
        // it's not necessary (InvokeRequired=false) doesn't
        // hurt, but it causes unnecessary overhead.
        if (InvokeRequired)
            Invoke(action);
        else
            action();

        // all controls and forms implement Invoke.
        // There is also BeginInvoke/EndInvoke which 
        // you can use to invoke asynchronously
    }
});
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

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
United States United States
Just a shiny lil monster. Casts spells in C++. Mostly harmless.

Comments and Discussions

 
QuestionI think there is a better way to do this. Pin
Member 1200388030-Apr-20 4:46
professionalMember 1200388030-Apr-20 4:46 
AnswerRe: I think there is a better way to do this. Pin
honey the codewitch30-Apr-20 5:12
mvahoney the codewitch30-Apr-20 5:12 
AnswerRe: I think there is a better way to do this. Pin
Padanian30-Apr-20 5:34
Padanian30-Apr-20 5:34 
GeneralRe: I think there is a better way to do this. Pin
honey the codewitch30-Apr-20 5:46
mvahoney the codewitch30-Apr-20 5:46 
QuestionPerformance Pin
Nelek29-Apr-20 23:50
protectorNelek29-Apr-20 23:50 
AnswerRe: Performance Pin
honey the codewitch29-Apr-20 23:52
mvahoney the codewitch29-Apr-20 23:52 
GeneralRe: Performance Pin
Nelek30-Apr-20 2:25
protectorNelek30-Apr-20 2:25 
GeneralRe: Performance Pin
honey the codewitch30-Apr-20 5:49
mvahoney the codewitch30-Apr-20 5:49 
AnswerRe: Performance Pin
Padanian30-Apr-20 3:36
Padanian30-Apr-20 3:36 
GeneralRe: Performance Pin
honey the codewitch30-Apr-20 3:48
mvahoney the codewitch30-Apr-20 3:48 
GeneralRe: Performance Pin
Padanian30-Apr-20 3:59
Padanian30-Apr-20 3:59 
GeneralRe: Performance Pin
honey the codewitch30-Apr-20 5:08
mvahoney the codewitch30-Apr-20 5:08 
GeneralRe: Performance Pin
Padanian30-Apr-20 5:38
Padanian30-Apr-20 5:38 
GeneralRe: Performance Pin
honey the codewitch30-Apr-20 5:46
mvahoney the codewitch30-Apr-20 5:46 
GeneralRe: Performance Pin
Nelek30-Apr-20 8:22
protectorNelek30-Apr-20 8:22 
GeneralRe: Performance Pin
honey the codewitch30-Apr-20 11:05
mvahoney the codewitch30-Apr-20 11:05 
GeneralRe: Performance Pin
honey the codewitch30-Apr-20 11:43
mvahoney the codewitch30-Apr-20 11:43 
GeneralRe: Performance Pin
Nelek30-Apr-20 13:05
protectorNelek30-Apr-20 13:05 
AnswerRe: Performance Pin
Stylianos Polychroniadis1-May-20 6:07
Stylianos Polychroniadis1-May-20 6:07 
GeneralRe: Performance Pin
honey the codewitch1-May-20 6:50
mvahoney the codewitch1-May-20 6:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.