Creating Home windows purposes, UI controls are certain to the UI thread. .NET made it to completely different iterations with completely different patterns coping with asynchronous programming. .NET 4.0 launched the Job Parallel Library (TPL) and C# 5 added the async and await key phrases. Along with these enhancements of .NET, and the synchronization context, invoking strategies that make use of various threads has changing into quite a bit simpler.
Just lately a reader of my e book Skilled C# and .NET – 2021 Version has requested me for extra info on the synchronization context has requested me for extra info on the synchronization context. My e book already has quite a bit info on duties, threads, async, and await – however let’s give it one other spin to demostrate some great benefits of the synchronization context.
Synchronization Context and Console Purposes
Earlier than utilizing a Home windows utility, let’s begin with a console utility creating a brand new job, and utilizing the async
/await
key phrases.
First, the strategy ShowThreadAndTask
shows the present thread id and job id. The id of the present thread could be accessed utilizing Setting.CurrentManagedThreadId
, the id of the duty with Job.CurrentId
. In all probability you additionally know the older API to entry the thread-id: Thread.CurrentThread.ManagedThreadId
.
With the top-level statements of a console utility, first the thread and job ids are displayed, subsequent the asynchronous technique SampleAsync
is invoked that makes use of Job.Run
to create a brand new job. With the await
key phrase, the execution of the top-level statements don’t proceed till the duty completes. Lastly, the thread and job ids are displayed once more.
Wanting on the ensuing output, you’ll be able to see that the top-level statements begin with a distinct thread than they finish with. With the next screenshot, the primary thread used has thread-id 1, after calling the SampleAsync technique, the thread-id is 4. You may get a distinct end result with each run of the applying. The highest-level statements don’t present a job identifier. Inside the Job.Run technique, the thread-id is 4, and there’s a task-id 1. When the duty completes, the identical thread is used after the async key phrase to run the remaining a part of the top-level statements. As a result of the duty accomplished, the identical thread now not is related to a job.
This is a vital level to know. Earlier than and after the async technique is known as, the thread can differ. With console purposes there’s no synchronization context, so the thread can change earlier than and after the async technique is known as.
Invoking the ConfigureAwait
technique you’ll be able to configure the conduct of the await to make use of a synchronization context and thus to proceed on the identical thread after the await. (ConfigureAwait(continueOnCapturedContext: true)). The default setting is true
, to proceed on the identical thread. Nonetheless, as a result of a console utility doesn’t have the synchronization context set, a context isn’t captured, and thus the continuation can proceed on a distinct thread with the default setting. When there’s no captuered context, altering the setting of ConfigureAwait
doesn’t change the conduct.
A console utility doesn’t have a synchronization context set as could be verified utilizing SynchronizationContext.Present
as proven within the following code snippet:
Blocking the present thread
Let’s proceed with a Home windows utility. I’m utilizing WinUI for this pattern. You’ll see comparable expertise with different UI applied sciences. The applying implements a easy calculator the place the person can add and subtract two values. The primary model of the Calculator specifies the BlockingAdd
technique which sleeps for a while earlier than returning a end result utilizing Thread.Sleep
. This technique is a blocking name. Calling this from the UI thread, the UI is frozen till the strategy completes. That’s why it’s best to by no means do that in a Home windows utility – don’t name blocking strategies from the UI thread. If you happen to attempt to run the applying clicking the Blocking Add button, the entire utility isn’t responding till the calculation is accomplished. You even can’t transfer the window. The UI thread is blocked.
If you should invoke such a blocking technique, you’ll be able to create a customized job as proven within the following code snippet with the Job
class:
![Creating a custom task]https://csharpdotchristiannageldotcom.information.wordpress.com/2022/08/winui-customtask.png)
Operating the applying you’ll be able to see that the UI is responsive this time. Nonetheless, the end result returned can’t be immediately assigned to the textbox. If this may be finished (you’ll be able to attempt to do away with the DispatcherQueue
used within the technique CustomTask
and immediately assign the end result to the textbox), and the applying would throw an exception. With WinUI it’s a COMException with the error RPC_E_WRONG_THREAD and the message The applying known as an interface that was marshalled for a distinct thread..
To invoke strategies or properties of UI parts, a change to the UI thread is required. With WinUI, this may be finished utilizing the DispatcherQueue
property of the DependencyObject
class. The DependencyObject
class is a base class of the WinUI parts. The DispatcherQueue
property property returns a DispatcherQueue
object that gives strategies to get again into the UI thread. You may move a DispatcherQueueHandler
delegate to the TryEnqueue
technique. The tactic referenced from the delegate is then known as from the UI thread. Behind the scenes that is finished passing info to the message queue of the UI thread, so the UI thread can execute this technique as quickly the thread is on the market to do that. Relying on how briskly this could occur, a precedence could be specified with the DispatcherQueuePriority
parameter. Strategies with a better precedence are executed earlier than strategies with a decrease precedence. The default precedence is DispatcherQueuePriority.Regular
.
You may create a timer with
DispatcherQueue.CreateTimer
to execute a technique on the UI thread primarily based on a time interval.
Do we have to change to the UI thread?
To test if a change to the UI thread is required, the DispatcherQueue
class presents the strategy HasThreadAccess
. This technique returns true if the present thread is the UI thread. The implementation within the following code snippet writes a string to a ListView
management. If the strategy is known as from the UI thread, a change isn’t required. With a distinct thread, the thread change is finished earlier than writing the string to the ListView
.
You may argue that you just don’t immediately write to UI parts from the code-behind, and as a substitute use a view-model. The MVVM sample is an efficient follow and in addition defined within the e book intimately in Chapter 30, Patterns with XAML Apps. Nonetheless, view-models don’t assist you on this situation. Setting properties which might be certain to UI parts must be finished from the UI thread, so you have got the identical drawback.
Utilizing WPF, there’s a option to fill an ObservableCollection from a background thread that’s certain to a UI factor for those who synchronize the entry. To specify the lock object for synchronizaton, you should use
BindingOperations.EnableCollectionSynchronization
.
Utilizing Async/Await with Home windows Purposes
Checking if the code is operating within the UI thread and switching to the UI thread in case it isn’t, requires some work. The synchronization context and the async/await key phrases makes this job straightforward.
Let’s change the implementation of the Add technique of the Calculator
class to make use of the async/await key phrases. The implementation proven within the following code snippet doesn’t block the calling thread. As a substitute of utilizing Thread.Sleep
, the strategy Job.Delay
returns a Job
that may be awaited. With the console utility you’ve seen {that a} completely different thread can run earlier than and after the await. That is now completely different with the Home windows utility. In any case, the calling thread isn’t blocked. With the AddAsync
technique, if the UI thread invokes this technique, after the delay is full, due to the synchronization context, the UI thread will proceed to return the end result. The SubtractAsync
technique is carried out otherwise. Utilizing the ConfigureAwait
technique, continueOnCapturedContext
is ready to false. The SubtractAsync technique doesn’t use the synchronization context, and after the await completes, a distinct thread can be utilized to run the remaining statements. In case the synchronization context isn’t wanted with the code after the await, you’ll be able to cut back the load of the UI thread and preserve a distinct thread at work. That is the case right here – with the SubtractAsync
technique, the subtraction could be finished from a distinct thread. Nonetheless, as this calculation is actually quick, it shouldn’t actually matter.
The next code snippet reveals the occasion handler technique implementation clicking the Add and Subtract buttons. The await
key phrase is used when calling the AddAsync
and SubtractAsync
strategies. As a result of this occasion handler is began from the UI thread, and ConfigureAwait isn’t used to alter the await conduct, the UI thread is used once more after the await. That’s why the end result could be immediately assigned to the Textual content
property of the TextBox
management with out doing a thread change.
Synchronization Context
Utilizing WinUI 3, SynchronizationContext.Present
returns a DispatcherQueueSynchronizationContext
object. With WPF, UWP, Home windows Varieties, and .NET MAUI you get different implementations of the concrete SynchronizationContext
class.
Remember on an necessary concern. In case you create a brand new job earlier than invoking an asynchronous technique, and wish the UI thread afterward: if a thread completely different from the UI thread is used with the await, this job doesn’t have the synchronization context to seize. Thus, after the await the change to the UI thread can not occur. So there are eventualities the place switching to the UI thread manually remains to be required in some instances.
Take away
UI parts are certain to the UI thread. If you should entry UI parts from a distinct thread, you should change to the UI thread. The work wanted to do that could be simplified with the async/await key phrases, and the assistance of the synchronization context. It’s nonetheless good to bear in mind what occurs behind the scenes, so in particular circuimstances you’ll be able to perceive the explanations when not every little thing works as anticipated.
Take pleasure in studying and programming!
Christian
You may assist my weblog by shopping for a espresso. Throughout these months all of the coffees (and extra) might be used to assist the Ukraine.