Synchronization Thread context in Task.Run but not waiting

After reading Steven Tub’s article on SynchronizationContext , I still have 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)); } 

Conclusion:

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 in WindowsFormsSynchronizationContext, as the article says that "SynchronizationContext.Current does not" flow "through the waitpoints" ...

The context is not passed when the PerformServiceCall function is called with Task.Run and an asynchronous lambda, for example:

 await Task.Run(async () => { await PerformServiceCall(); }).ConfigureAwait(false); 

Can anyone clarify or point out any documentation on this?

+5
source share
1 answer

Stephen's article explains that the SynchronizationContext does not "flow" as the ExecutionContext does (although the SynchronizationContext is part of the ExecutionContext ).

ExecutionContext always flowing. Even if you use Task.Run , therefore, if the SynchronizationContext will flow with it, Task.Run will be executed in the user interface thread, and therefore Task.Run will be pointless. SynchronizationContext does not flow, it is more likely to be fixed when an asynchronous point is reached (i.e. await ), and continued after its publication (unless explicitly stated otherwise).

The difference is explained in this quote:

Now we have a very important point: flowing ExecutionContext semantically very different from capturing and placing on a SynchronizationContext .

With an ExecutionContext thread, you grab state from a single thread, and then restore that state to its environment during the execution of delegated delegates. This is not what happens when capturing and using a SynchronizationContext . The capture part is the same as you capture data from the current stream, but then use this state in different ways. Instead of making this state current during a delegate call, SynchronizationContext.Post you simply use this captured state to call a delegate. Where and when and how this delegate is executed depends entirely on the implementation of the Post method.

In your case, this means that when you PerformServiceCall 1 Current SynchronizationContext really WindowsFormsSynchronizationContext because you have not reached any asynchronous point yet and are still in the user interface thread (remember that the part before the first await in the async method will be executed synchronously in the calling thread, therefore LogSyncContext("PerformServiceCall 1"); will happen before ConfigureAwait(false) in the task returned from PerformServiceCall ).

You only "go" with the UI SynchronizationContext when you use ConfigureAwait(false) (which does not take into account the captured SynchronizationContext ). This happens for the first time on HttpClient.GetAsync , and then again on PerformServiceCall .

+6
source

Source: https://habr.com/ru/post/1216586/


All Articles