Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding what multiple configureawait(false) do in a single async method

Consider this code:

public async Task SomeMethodAsync(){
    //1. code here executes on the original context

    //for simplicity sake, this doesn't complete instantly 
    var result1 = await Method1Async().ConfigureAwait(false);

    //2. code here doesn't executes in the original context 

Now for my question, if in the above method there was another call to an async method:

    //again for simplicity sake, this doesn't complete instantly 
    var result2 = await Method2();
    //3. code in question is here
}

Will the code in question run in the original context or in another context (maybe a thread from the thread pool or another context)?

Also, if method2 would have been called with ConfigureAwait(false) will the code in question run in the same context as segment number 2?

like image 256
jon smith Avatar asked Mar 31 '18 23:03

jon smith


1 Answers

Simplified Original Code

//for simplicity sake, this doesn't complete instantly 
var result1 = await Method1Async().ConfigureAwait(false);

//again for simplicity sake, this doesn't complete instantly 
var result2 = await Method2();

//code in question is here

Answers

Assuming that your async methods don't complete instantly, here are two short answers.

Will the code in question run in the original context or in another context (maybe a thread from the thread pool or another context)?

The code in question will have a null SynchronizationContext.Current value and will thereby run in a default SynchronizationContext.

Regarding what thread the code in question runs in: the SynchronizationContext makes that decision. The code gets posted/sent to a SynchronizationContext, which in turn forwards the operation to a specific computational resource, such as a specific thread, specific CPU Core, or something else. In the case of the default SynchronizationContext, the choice of thread depends on what is hosting your application (e.g. console app vs ASP.NET app). In the case of a non-default SynchronizationContext, the choice of computational resource depends on the whim of the implementer: it could run on a network share.

Also, if method2 would have been called with ConfigureAwait(false) will the code in question run in the same context as segment number 2?

If method2 had ConfigureAwait(false), then the code in question would also run in a default SynchronizationContext. In other words, when we use false, the Task no longer tries to continue in a captured context.

Experiment

Here is an experiment (the full listing is here) that can answer both of your questions.

The experiment uses a SynchronizationContext that maintains a simple string State, resets itself as the current context during Post, and overrides ToString() to output its State value.

public class MySyncContext : SynchronizationContext
{
    public string State { get; set; }

    public override void Post(SendOrPostCallback callback, object state)
    {
        base.Post(s => { 
            SynchronizationContext.SetSynchronizationContext(this);
            callback(s);
        }, state);
    }

    public override string ToString() => State;
}

What that does it let us see whether code is running in the original context or not.

So, lets recall what it is that you asked:

Will the code in question run in the original context or in another context (maybe a thread from the thread pool or another context)?

To answer that question we have an experiment that is close to your your setup. It first sets the original SynchronizationContext with a known state and then awaits two async methods, using ConfigureAwait(false) on the first, logging the current SynchronizationContext along the way.

static async Task Run()
{
    var syncContext = new MySyncContext { State = "The Original Context" };
    SynchronizationContext.SetSynchronizationContext(syncContext);

    Console.WriteLine("Before:" + SynchronizationContext.Current);

    await Task.Delay(1000).ConfigureAwait(false);
    Console.WriteLine("After Result1:" + SynchronizationContext.Current);

    await Task.Delay(1000);
    Console.WriteLine("After Result2:" + SynchronizationContext.Current);
}

You're wondering whether code that runs after the second method will run in the original context or not. The output answers that. Neither the first nor the second async method post their continuations to the original context.

The code above with ConfigureAwait(false) outputs this:

Before:The Original Context
After Result1:                       
After Result2:               

And if we change the above code to have ConfigureAwait(true), both methods run their continuations in the original context, and the output is this:

Before:The Original Context        
After Result1:The Original Context   
After Result2:The Original Context

So there you have it. It was enlightening for me to run the full code listing with various combinations of true and false, with multiple different SynchronizationContext values, and with delays of 0 to see what happens.

It was also worth reading parts of What does SynchronizationContext do? and It's All About the SynchronizationContext

like image 177
Shaun Luttin Avatar answered Sep 30 '22 18:09

Shaun Luttin