What’s the SynchronizationContext used for?

Creating Windows applications, UI controls are bound to the UI thread. .NET made it to different iterations with different patterns dealing with asynchronous programming. .NET 4.0 introduced the Task Parallel Library (TPL) and C# 5 added the async and await keywords. Together with these enhancements of .NET, and the synchronization context, invoking methods that make use of different threads has becoming a lot easier.

Recently a reader of my book Professional C# and .NET – 2021 Edition has asked me for more information on the synchronization context has asked me for more information on the synchronization context. My book already has a lot information on tasks, threads, async, and await – but let’s give it another spin to demostrate the advantages of the synchronization context.

Railroad track switch

Synchronization Context and Console Applications

Before using a Windows application, let’s start with a console application creating a new task, and using the async/await keywords.

First, the method ShowThreadAndTask displays the current thread id and task id. The id of the current thread can be accessed using Environment.CurrentManagedThreadId, the id of the task with Task.CurrentId. Probably you also know the older API to access the thread-id: Thread.CurrentThread.ManagedThreadId.

Show Thread and Task Ids

With the top-level statements of a console application, first the thread and task ids are displayed, next the asynchronous method SampleAsync is invoked that uses Task.Run to create a new task. With the await keyword, the execution of the top-level statements do not continue until the task completes. Finally, the thread and task ids are displayed again.

Creating a Task and calling an async method

Looking at the resulting output, you can see that the top-level statements start with a different thread than they end with. With the following screenshot, the first thread used has thread-id 1, after calling the SampleAsync method, the thread-id is 4. You might get a different result with every run of the application. The top-level statements don’t show a task identifier. Within the Task.Run method, the thread-id is 4, and there’s a task-id 1. When the task completes, the same thread is used after the async keyword to run the remaining part of the top-level statements. Because the task completed, the same thread no longer is associated with a task.

Running the application

This is an important point to understand. Before and after the async method is called, the thread can differ. With console applications there’s no synchronization context, so the thread can change before and after the async method is called.

Invoking the ConfigureAwait method you can configure the behavior of the await to use a synchronization context and thus to continue on the same thread after the await. (ConfigureAwait(continueOnCapturedContext: true)). The default setting is true, to continue on the same thread. However, because a console application doesn’t have the synchronization context set, a context is not captured, and thus the continuation can continue on a different thread with the default setting. When there’s no captuered context, changing the setting of ConfigureAwait doesn’t change the behavior.

A console application doesn’t have a synchronization context set as can be verified using SynchronizationContext.Current as shown in the following code snippet:

Checking the synchronization context

Blocking the current thread

Let’s continue with a Windows application. I’m using WinUI for this sample. You’ll see similar experience with other UI technologies. The application implements a simple calculator where the user can add and subtract two values. The first version of the Calculator specifies the BlockingAdd method which sleeps for some time before returning a result using Thread.Sleep. This method is a blocking call. Calling this from the UI thread, the UI is frozen until the method completes. That’s why you should never do this in a Windows application – don’t call blocking methods from the UI thread. If you try to run the application clicking the Blocking Add button, the complete application is not responding until the calculation is completed. You even can’t move the window. The UI thread is blocked.

BlockingAdd

If you need to invoke such a blocking method, you can create a custom task as shown in the following code snippet with the Task class:

![Creating a custom task]https://csharpdotchristiannageldotcom.files.wordpress.com/2022/08/winui-customtask.png)

Running the application you can see that the UI is responsive this time. However, the result returned cannot be directly assigned to the textbox. If this would be done (you can try to get rid of the DispatcherQueue used in the method CustomTask and directly assign the result to the textbox), and the application would throw an exception. With WinUI it’s a COMException with the error RPC_E_WRONG_THREAD and the message The application called an interface that was marshalled for a different thread..

To invoke methods or properties of UI elements, a switch to the UI thread is needed. With WinUI, this can be done using the DispatcherQueue property of the DependencyObject class. The DependencyObject class is a base class of the WinUI elements. The DispatcherQueue property property returns a DispatcherQueue object that offers methods to get back into the UI thread. You can pass a DispatcherQueueHandler delegate to the TryEnqueue method. The method referenced from the delegate is then called from the UI thread. Behind the scenes this is done passing information to the message queue of the UI thread, so the UI thread can execute this method as soon the thread is available to do this. Depending on how fast this should happen, a priority can be specified with the DispatcherQueuePriority parameter. Methods with a higher priority are executed before methods with a lower priority. The default priority is DispatcherQueuePriority.Normal.

You can create a timer with DispatcherQueue.CreateTimer to execute a method on the UI thread based on a time interval.

Do we need to switch to the UI thread?

To check if a switch to the UI thread is required, the DispatcherQueue class offers the method HasThreadAccess. This method returns true if the current thread is the UI thread. The implementation in the following code snippet writes a string to a ListView control. If the method is called from the UI thread, a switch is not required. With a different thread, the thread switch is done before writing the string to the ListView.

Check if a thread switch is required

You might argue that you don’t directly write to UI elements from the code-behind, and instead use a view-model. The MVVM pattern is a good practice and also explained in the book in detail in Chapter 30, Patterns with XAML Apps. However, view-models don’t help you in this scenario. Setting properties that are bound to UI elements needs to be done from the UI thread, so you have the same problem.

Using WPF, there’s a way to fill an ObservableCollection from a background thread that is bound to a UI element if you synchronize the access. To specify the lock object for synchronizaton, you can use BindingOperations.EnableCollectionSynchronization.

Using Async/Await with Windows Applications

Checking if the code is running in the UI thread and switching to the UI thread in case it is not, requires some work. The synchronization context and the async/await keywords makes this task easy.

Let’s change the implementation of the Add method of the Calculator class to use the async/await keywords. The implementation shown in the following code snippet does not block the calling thread. Instead of using Thread.Sleep, the method Task.Delay returns a Task that can be awaited. With the console application you’ve seen that a different thread can run before and after the await. This is now different with the Windows application. In any case, the calling thread is not blocked. With the AddAsync method, if the UI thread invokes this method, after the delay is complete, because of the synchronization context, the UI thread will continue to return the result. The SubtractAsync method is implemented differently. Using the ConfigureAwait method, continueOnCapturedContext is set to false. The SubtractAsync method doesn’t use the synchronization context, and after the await completes, a different thread can be used to run the remaining statements. In case the synchronization context is not needed with the code after the await, you can reduce the load of the UI thread and keep a different thread at work. This is the case here – with the SubtractAsync method, the subtraction can be done from a different thread. However, as this calculation is really fast, it shouldn’t really matter.

Async calculator

The following code snippet shows the event handler method implementation clicking the Add and Subtract buttons. The await keyword is used when calling the AddAsync and SubtractAsync methods. Because this event handler is started from the UI thread, and ConfigureAwait is not used to change the await behavior, the UI thread is used again after the await. That’s why the result can be directly assigned to the Text property of the TextBox control without doing a thread switch.

Invoking the Async Calculator

Synchronization Context

Using WinUI 3, SynchronizationContext.Current returns a DispatcherQueueSynchronizationContext object. With WPF, UWP, Windows Forms, and .NET MAUI you get other implementations of the concrete SynchronizationContext class.

Be aware on an important issue. In case you create a new task before invoking an asynchronous method, and need the UI thread later on: if a thread different from the UI thread is used with the await, this task doesn’t have the synchronization context to capture. Thus, after the await the switch to the UI thread cannot happen. So there are scenarios where switching to the UI thread manually is still required in some cases.

Take away

UI elements are bound to the UI thread. If you need to access UI elements from a different thread, you need to switch to the UI thread. The work needed to do this can be simplified with the async/await keywords, and the help of the synchronization context. It’s still good to be aware what happens behind the scenes, so in special circuimstances you can understand the reasons when not everything works as expected.

Enjoy learning and programming!

Christian

You can support my blog by buying a coffee. During these months all the coffees (and more) will be used to support the Ukraine.

Buy Me A Coffee

More Information

More information about programming EF Core is available in my new book and my workshops.

Read more about threads, tasks, and WinUI applications in my book Professional C# and .NET – 2021 Edition

See Chapter 11, "Tasks and Asynchronous Programming" for more information about the async/await keywords and the Task class.

See Chapter 17, "Parallel Programming" for more information on threads, threading issues, a lot more information on the Task class, timers, synchronization objects, and more.

See Chapters 29 to 31 for information on using WinUI to create Windows applications.

Trainings

Sample source code

The complete source code of this sample is available on Professional C# source code in the folder 5_More.

Photo railroad track switch ID 21656104 © Jerryb8 | Dreamstime.com

Advertisement

3 thoughts on “What’s the SynchronizationContext used for?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.