Why is Async callback executed in WPF UI thread

Regarding this piece of code:

static async Task<string> testc() { Console.WriteLine("helo async " + Thread.CurrentThread.ManagedThreadId); await Task.Run(() => { Thread.Sleep(1000); Console.WriteLine("task " + Thread.CurrentThread.ManagedThreadId); }); Console.WriteLine("callback "+Thread.CurrentThread.ManagedThreadId); return "bob"; } static void Main(string[] args) { Console.WriteLine("helo sync " + Thread.CurrentThread.ManagedThreadId); testc(); Console.WriteLine("over" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(2000); Console.ReadLine(); } 

I get the following output:

 helo sync 10 helo async 10 over10 task 11 callback **11** 

This is normal: after waiting, part of the code is executed in the same thread as the task itself.

Now, if I do this in a WPF application:

 private void Button_Click_1(object sender, RoutedEventArgs e) { Console.WriteLine("helo sync " + Thread.CurrentThread.ManagedThreadId); testc(); Console.WriteLine("over" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(2000); Console.ReadLine(); } 

It generates the following output:

 helo sync 8 helo async 8 over8 task 9 callback **8** 

Where we can see the code after waiting in the user interface thread. Well, that's great, as it allows you to manipulate observable collections, etc. But I was wondering: "Why?" "How could I do the same?" Is this related to the behavior of TaskScheduler? Is this hardcoded in the .NET Framework?

Thanks for any idea you can send.

+7
source share
3 answers

The reason is because Task.Run will capture the SynchronizationContext if it is present, as in the WPF application, when the task starts from the user interface thread. Task will then use the SynchronizationContext to serialize the callback to the user interface thread. However, if the context is not accessible, as in the console application, the callback will occur in another thread.

Stephen Tube described this in a recording.

BTW, be careful when using never use Thread.Sleep in a task. This can cause strange behavior because the task cannot be bound to a single thread. Use Task.Delay instead.

+7
source

But I was wondering: "Why?"

You yourself answered:

Well, that's great, as it allows you to manipulate observable collections, etc.

The whole point of async is to make working with asynchrony easier, so you can write โ€œsynchronousโ€ code that is actually asynchronous. This often involves having to stick to the same context (for example, the user interface thread) for the entire async method - simply โ€œpausingโ€ the method (without blocking the user interface thread) when you need to wait for something.

"How could I do the same?"

It is not clear what you mean here. Basically, the implementation of the expected template for Task uses TaskScheduler.FromCurrentSynchronizationContext() to determine which scheduler should send a call back - unless you use ConfigureAwait(false) to explicitly reject this behavior. So how he deals with it ... whether you can โ€œdo the sameโ€ depends on what you are trying to do.

See the async / await FAQ for what is expected for more information about the expected template.

+2
source

You can find my async / await intro . Other answers are almost correct.

When you await a Task that has not yet been completed, the default is the "context" that is used to resume the method when Task finishes. This "context" SynchronizationContext.Current , if it is not equal to zero, in which case it is TaskScheduler.Current .

Pay attention to the conditions necessary for this:

  • "When you await ..." - if you plan to continue manually, for example, using Task.ContinueWith , contextual capture is not performed. You have to do it yourself using something like (SynchronizationContext.Current == null ? TaskSchedler.Current : TaskScheduler.FromCurrentSynchronizationContext()) .
  • "... await a Task ..." - this behavior is part of the await behavior for Task types. Other types may or may not perform a similar capture.
  • "... which is not completed yet ..." - if Task already completed by the time it is await ed, the async method will continue synchronously. Therefore, there is no need to fix the context in this case.
  • "... default ..." is the default behavior and can be changed. In particular, call the Task.ConfigureAwait method and pass false for the continueOnCapturedContext parameter. This method returns the expected type (not Task ), which will not continue in the captured context if its parameter was false .
0
source

All Articles