5

I was expecting to get back to Thread#1 at location 1.2, but I did not. Is there a way to get back to UI thread after making the async call? Thanks

Also I cannot make the top level method async. Not sure if async all the way will solve this issue but I don't have that choice right now.

class Program
{
    static void Main(string[] args)
    {
        ComputeThenUpdateUI().Wait();
    } 
    static async Task ComputeThenUpdateUI()
    {
        Console.WriteLine($"1.1 {Thread.CurrentThread.ManagedThreadId}");
        await ExpensiveComputingNonUI().ConfigureAwait(true);
        Console.WriteLine($"1.2 {Thread.CurrentThread.ManagedThreadId}");
    } 
    static async Task ExpensiveComputingNonUI()
    {
        Console.WriteLine($"2.1 {Thread.CurrentThread.ManagedThreadId}");
        await Task.Delay(3000).ConfigureAwait(false);
        Console.WriteLine($"2.2 {Thread.CurrentThread.ManagedThreadId}");
        await Task.Delay(3000).ConfigureAwait(false);
        Console.WriteLine($"2.3 {Thread.CurrentThread.ManagedThreadId}");
    }
}
 
 
Output:
1.1 1
2.1 1
2.2 4
2.3 4
1.2 4

Carol
  • 363
  • 5
  • 16
  • It depends on the synchronization context that is installed, which depends on what kind of "app" you are using. By default, console applications don't have a synchronization context, so they will act differently than something like WPF, or ASP.NET. – Bradley Uffner May 31 '18 at 03:55

2 Answers2

7

Re : Doesn't .ConfigureAwait(true) mean that flow will return to the same thread once the await completes?

No, in most cases, there is no guarantee. Stephen Cleary has a definitive answer:

This "context" is SynchronizationContext.Current unless it is null, in which case it is TaskScheduler.Current. (If there is no currently-running task, then TaskScheduler.Current is the same as TaskScheduler.Default, the thread pool task scheduler).

It's important to note that a SynchronizationContext or TaskScheduler does not necessarily imply a particular thread. A UI SynchronizationContext will schedule work to the UI thread; but the ASP.NET SynchronizationContext will not schedule work to a particular thread.

i.e. only calls made from the UI thread of e.g. WPF or Windows Forms are guaranteed to return to the same thread. If you really need to return to the same thread, then you'd need to do customization to ensure the continuation is scheduled on the original thread.

Unless this is essential (such as for UI threads), returning to the same thread is generally undesirable as this can reduce performance (if there is contention for the thread) and lead to deadlocks

Re: Cannot make the top level main method async

This is now possible in C#7.1:

public static async Task Main(string[] args)
{
    // Can await asynchronous tasks here.
}
Community
  • 1
  • 1
StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • Thanks for the detailed reply. I simplified the real logic in my example. I guess I simlified in a misleading way :P In the production code I am working on, the top level caller is a synchronous method which I cannot change the signature, and I need to do some non ui related computing then write the result to UI element. So: 1) the top level caller is in UI thread, 2) I still notice the thread is no in UI thread after ConfigureAwait(true) probably because there are some ConfigAwait(false) calls inside the callee... – Carol May 31 '18 at 06:05
  • Can you perhaps update your question with a sample starting from your UI, and specify what 'kind' of UI you are dealing with, e.g. WPF, WinForms, Xamarin, etc. `ConfigureAwait(false)` inside lower level library code will not affect any high level awaits - it just skips the restoration of any `SynchronizationContext` in the library calls. If the thread is NOT in the UI thread after the await (and without an explicit `.ConfigureAwait(false)`) it means that the async call didn't capture the UI Synch Context – StuartLC May 31 '18 at 06:28
  • i.e. The problem seems to be that you aren't starting the `async` chain from the UI thread as you currently believe. – StuartLC May 31 '18 at 07:18
3

ConfigureAwait is a local decision, within each method that you write. ConfigureAwait(true) is a way of indicating that you want to try to return to the same context in which your method was running when it was first entered.

Firstly, this means that if you don't know what context you were originally running in then there's not a great deal of point in trying to get back to that same context. You shouldn't be trying to do anything that is context-dependent if you can be called in a variety of contexts. This is why it's usually good advice to use ConfigureAwait(false) if you're authoring a library. You will not know the contexts in which your code is called.

And secondly, it means that there is no "global" context to return to. If your caller (or any of it's callers, etc, higher up the chain) have already separated themselves from their original calling context, there's no way (via this mechanism) to return to that context.

If you want to switch to a specific context (such as a UI thread) and that's not guaranteed to be your calling context, you need to use a different, context-specific mechanism, such as Dispatcher.Invoke() or whatever is appropriate in your environment.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448