After reading Stephen Toub's article on SynchronizationContext I'm left with a question about the output of this piece of .NET 4.5 code:
private void btnDoSomething_Click()
{
LogSyncContext("btnDoSomething_Click");
DoItAsync().Wait();
}
private async Task DoItAsync()
{
LogSyncContext("DoItAsync");
await PerformServiceCall().ConfigureAwait(false); //to avoid deadlocking
}
private async Task PerformServiceCall()
{
LogSyncContext("PerformServiceCall 1");
HttpResponseMessage message = await new HttpClient
{
BaseAddress = new Uri("http://my-service")
}
.GetAsync("/").ConfigureAwait(false); //to avoid deadlocking
LogSyncContext("PerformServiceCall 2");
await ProcessMessage(message);
LogSyncContext("PerformServiceCall 3");
}
private async Task ProcessMessage(HttpResponseMessage message)
{
LogSyncContext("ProcessMessage");
string data = await message.Content.ReadAsStringAsync();
//do something with data
}
private static void LogSyncContext(string statementId)
{
Trace.WriteLine(String.Format("{0} {1}", statementId, SynchronizationContext.Current != null ? SynchronizationContext.Current.GetType().Name : TaskScheduler.Current.GetType().Name));
}
The output is:
btnDoSomething_Click WindowsFormsSynchronizationContext
DoItAsync WindowsFormsSynchronizationContext
PerformServiceCall 1 WindowsFormsSynchronizationContext
PerformServiceCall 2 ThreadPoolTaskScheduler
ProcessMessage ThreadPoolTaskScheduler
PerformServiceCall 3 ThreadPoolTaskScheduler
But I would expect PerformServiceCall 1 to not be on the WindowsFormsSynchronizationContext since the article states that "SynchronizationContext.Current does not “flow” across await points"...
The context does not get passed when calling PerformServiceCall with Task.Run and an async lambda, like this:
await Task.Run(async () =>
{
await PerformServiceCall();
}).ConfigureAwait(false);
Can anyone clarify or point to some documentation on this?
If it is some trivial operation that executes quickly, then you can just call it synchronously, without the need for await . But if it is a long-running operation, you may need to find a way to make it asynchronous.
If you don't await the task or explicitly check for exceptions, the exception is lost. If you await the task, its exception is rethrown. As a best practice, you should always await the call. By default, this message is a warning.
The main purpose of Task. Run() is to execute CPU-bound code in an asynchronous way. It does this by pulling a thread from the thread pool to run the method and returning a Task to represent the completion of the method.
No, async await is just made to allow code to run whilst something else is blocking, and it doesn't do Task.
Stephen's article explains that SynchronizationContext
doesn't "flow" as ExecutionContext
does (although SynchronizationContext
is a part of the ExecutionContext
).
ExecutionContext
is always flowed. Even when you use Task.Run
so if SynchronizationContext
would flow with it Task.Run
would execute on the UI thread and so Task.Run
would be pointless.
SynchronizationContext
doesn't flow, it rather gets captured when an asynchronous point (i.e. await
) is reached and the continuation after it is posted to it (unless explicitly stated otherwise).
The difference is explained in this quote:
Now, we have a very important observation to make: flowing
ExecutionContext
is semantically very different than capturing and posting to aSynchronizationContext
.When you flow
ExecutionContext
, you’re capturing the state from one thread and then restoring that state such that it’s ambient during the supplied delegate’s execution. That’s not what happens when you capture and use aSynchronizationContext
. The capturing part is the same, in that you’re grabbing data from the current thread, but you then use that state differently. Rather than making that state current during the invocation of the delegate, withSynchronizationContext.Post
you’re simply using that captured state to invoke the delegate. Where and when and how that delegate runs is completely up to the implementation of thePost
method.
That means in your case that when you output PerformServiceCall 1
The current SynchronizationContext
is indeed WindowsFormsSynchronizationContext
because you haven't yet reached any asynchronous point and you are still in the UI thread (keep in mind that the part before the first await
in an async
method is executed synchronously on the calling thread so LogSyncContext("PerformServiceCall 1");
happens before ConfigureAwait(false)
happens on the task returned from PerformServiceCall
).
You only "get off" the UI's SynchronizationContext
when you use ConfigureAwait(false)
(which disregards the captured SynchronizationContext
). The first time that happens is on HttpClient.GetAsync
and then again on PerformServiceCall
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With