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.
Synchronization Context and Console Applications
Before using a Windows application, let’s start with a console application creating a new task, and using the
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:
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.
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.
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.
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:
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.
If you need to invoke such a blocking method, you can create a custom task as shown in the following code snippet with the
![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
You can create a timer with
DispatcherQueue.CreateTimerto 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
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
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
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.
The following code snippet shows the event handler method implementation clicking the Add and Subtract buttons. The
await keyword is used when calling the
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.
Using WinUI 3,
SynchronizationContext.Current returns a
DispatcherQueueSynchronizationContext object. With WPF, UWP, Windows Forms, and .NET MAUI you get other implementations of the concrete
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.
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!
You can support my blog by buying a coffee. During these months all the coffees (and more) will be used to support the Ukraine.
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.
The complete source code of this sample is available on Professional C# source code in the folder 5_More.