jon smith
jon smith

Reputation: 83

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?

Upvotes: 6

Views: 1772

Answers (3)

Shaun Luttin
Shaun Luttin

Reputation: 141542

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

Upvotes: 11

Hitesh P
Hitesh P

Reputation: 416

If you use ConfigureAwait at some point in your method, it is recommended to use for every Await in the method. The reason is that the context is only captured if an incomplete task is awaited. Context won't be captured if the task is already complete. Also bear in mind that things like hardware and network plays role in how long it takes to execute the task.

The answer to the first question depends on these things. If the Method1Async() completed without awaiting, Code at no. 2 can still be executed in original context and so the code at no. 3

Providing Method1Async() is awaited, it manages to detach from the original context. The rest of the method will be executed on threadpool context, along with the call to the method2 on the same threadpool context. Answer to your second question will be yes.

Upvotes: 1

glenebob
glenebob

Reputation: 1973

ConfigureAwait(false) basically throws away the context that was associated with the Task. Therefore, when that Task calls its continuation, the continuation will also have no context. Since the second async method is called from within the continuation of the first, its Task will also have no context. Calling ConfigureAwait(false) on a task with no context has no effect.

Upvotes: 0

Related Questions