Synchronizing calls to the UI in a multi-threaded application
More and more we are forced to write multi-threaded or asynchronous applications to e.g. leverage the many cores or cpu’s of our servers and personal computers.
But when dealing with multiple threads or asynchronous callbacks we often have to synchronize worker threads with the UI thread since visual components cannot be accessed from another thread that the one they have been instantiated in.
The synchronization of threads can be a tedious and error prone task if you don’t approach it correctly.
How many times have you seen code like the one below in a multi threaded WinForms application?
This code fragment checks whether it is running in the UI thread or not. If it is not running in the UI thread the call has to be synchronized with the UI thread by calling the BeginInvoke() method of the underlying control.
I confess that not too many years ago – maybe 3 or so – I was guilty of having implemented similar code as well. Then I got sick of all this repetitive code. Don’t repeat yourself (DRY) has always been one of my primary goals during my career as a developer. This was the time when I discovered that there is such a thing called SynchronizationContext in the .NET framework. I was then able to implement an event broker which handled all the synchronization for me and my UI code was freed from the burden of synchronization.
Lately a friend of mine came to me with the same kind of code I showed just above. He complained that such code sucks. I told him “You are right man! Been there…”
So let’s find a solution for this problem. I want to free up the UI code from having to synchronize callbacks to the UI thread. This should be done (auto-magically) by the infrastructure.
Approaching a solution
Working with events
Imagine having a stock quote service that periodically returns you the current quote for a given symbol. The quote service runs in a background thread and notifies the UI thread via an event. Let me first show an implementation where the burden of thread synchronization has to be taken by the consumer of the quote service.
The service is started by calling the StartWorking() method. This method spawns a new thread and starts it. The method that runs in the new (background-) thread is the Run() method. The Run() method runs infinitely until it is stopped by calling the Stop() method.
The Run() method gets active about every 500 ms. It tries to get a new quote from the stock exchange. The real stock exchange is just simulated with a very stupid algorithm here. Once a new quote is available a QuoteChanged event is raised such as the consumers can react. Unfortunately the event is raised on the same thread as the quote service is running, that is in the background thread.
So if the consumer has to update the UI we have a problem. The consumer needs to synchronize the callback from the quote service with the UI thread and only then can the UI be updated. Thus code similar to the one shown in the introduction is needed.
How can I force the quote service to trigger the QuoteChanged event on the UI thread? It’s easy when using the SynchronizationContext class of the .NET framework mentioned before. I introduce a new instance variable for the quote service
and initialize this field in the constructor
Note that the AsyncOperationManager is another class of the .NET framework. It returns you the correct synchronization context for all application models supported by the .NET framework. In the case of a WinForms application the property SynchronizationContext returns an instance of type WindowsFormsSynchronizationContext.
And finally I slightly modify my Run() method
Instead of directly calling the OnQuoteChanged(…) method I use the synchronization context and post the call to OnQuoteChanged. The latter method is then executed on the UI thread. My consumer is now freed from the burden of having to synchronize any callback from the quote service.
Below I show a code fragment how a consumer could be implemented.
Here the consumer is a form (in a WinForms application). When the user clicks on a start button the quote service is initialized. The callback function to be executed when the QuoteChanged event is triggered is defined via a lambda expression. This time the new quote is shown in the label lblQuote. As you can see: there is absolutely no synchronization code needed in the consumer!
Working by registering lambda expressions
Now I want to present a asynchronous service where consumers can register lambda expressions as callback functions. Let me implement a temperature service. This service executes all registered lambda expressions whenever the temperature has changed.
The code is very similar to the one of the quote service presented in the previous section. But here the service exposes no events. Instead it has a Register(…) method where consumers can register themselves with a a lambda expression (or callback method). The lambda expression must have one argument of type double which corresponds to the temperature.
The service notifies all registered consumers every 1000 ms by calling their respective lambda expression and providing the current temperature (in the method OnTemperatureChanged(…)).
The consumer code looks very similar to the one presented in the previous section
The only difference being the call to the Register(…) method instead of “consuming an event”.
Well that’s it, or is it? No! That cannot be the final solution… Our services are now in charge of the synchronization. This is only a shift of the burden not a real solution! We have to find a more generic solution!
Working with a message broker or message bus
Let’s lean back for a moment and reflect on what really happens. What we are really doing is to exchange messages between (service-) providers and consumers. So what if we could just introduce a man in the middle which does the dirty task of synchronization for us? Any provider just posts its messages to this man in the middle (let us call it message broker for now). And any consumer registers with the broker for specific types of messages. The broker then just receives messages and distributes them to all registered consumers. Before distributing a message it synchronizes the threads.
The code below shows a (very simplistic) implementation of such a message broker. A provider can use the **Send
Please note that the synchronization with the UI thread happens in the last line of the Send<> method.
To access the message broker from allover my application I use the singleton pattern. I just define a static wrapper MessageBus around my broker.
Note that in a real application I would “never” use this approach but rather use an IoC container which provides me access to the message broker via dependency injection (DI). But for this simple sample I didn’t want to introduce DI and IoC.
I can now modify my quote service such as that it uses the message broker. Then the Run() method looks like this
Instead of QuoteEventArgs I use a message of type QuoteMessage to transport data. The implementation of this message type is trivial
The consumer code is shown below
In the OnLoad(…) method of the form I register the form as a consumer of messages of type QuoteMessage and pass as a parameter the lambda expression that shall be executed whenever such a message is available.
Working with multi-threaded or asynchronous applications is a little bit harder than with single-threaded applications. One of the problems is that code running in background threads cannot directly access visual components of the UI. One has to synchronize the background thread with the UI thread. This post presented various approaches how one can solve this problem. The goal of this post is to introduce a possible solution which generalizes the thread synchronization and concentrates it into a central place (into the infrastructure of the application) such as that the code in the presentation logic and/or in the services does not have to care about synchronization. The solution presented here is a message broker or message bus. Service providers can post messages to the broker and consumers can register themselves for specific messages. The thread synchronization is handled by the broker.