Understanding context in C# 5 async/await
Asked Answered
D

2

30

Am I correct that async/await itself has nothing to do with concurrency/parallelism and is nothing more than continuation-passing style (CPS) implementation? And the real threading is performed by SynchronizationContext instance that await passes/restores?

If that is correct I have the following question about SynchronizationContext:
it guarantees that a continuation will be executed on the same thread.

However, are there any guaranties that the thread's context information is persisted? I mean Name, CurrentPrincipal, CurrentCulture, CurrentUICulture, etc. Does it depend on framework (ASP.NET, WinForms, WCF, WPF)?

Dyarchy answered 17/9, 2012 at 9:5 Comment(2)
In general, SynchronizationContext does not guarantee that continuation will be executed on the same thread. Some contexts to that (WPF, Winforms), but others don't (ASP.NET).Ormond
You can set a default culture for threads like this.Hornstone
C
45

Am i correct that async/await itself has nothing to do with concurrency/parallelism and is nothing more than CPS implementation?

Well, async / await is a rewriting that uses CPS, so your core understanding is correct.

Regarding "concurrency" and "parallelism", I would say that it does enable concurrency; you can start multiple async operations which are all "in flight" at the same time. This is easy to do with Task.WhenAll and Task.WhenAny.

Also, even though async by itself doesn't imply "multithreading", Task.Run does enable easy async-compatible multithreading

And the real threading is performed by SynchronizationContext instance that await passes/restores?

Think of it this way: the continuation created by the CPS rewriting has to run somewhere. The captured "async context" can be used to schedule the continuation.

Side note: the captured context is actually SynchronizationContext.Current unless it is null, in which case the captured context is TaskScheduler.Current.

Another important note: the capturing and restoring of the context is actually up to the "awaiter" object. So, by default, if you await a Task (or any other built-in awaitable), the context will be captured and restored. But if you await the result of ConfigureAwait(false), then the context is not captured. Similarly, if you await your own custom awaitable, it won't capture the context (unless you program it to).

However, are there any guaranties that the thread's context information is persisted? I mean Name, CurrentPrincipal, CurrentCulture, CurrentUICulture, etc.

SynchronizationContext is different than ExecutionContext. A simplified answer is that ExecutionContext always "flows", so CurrentPrincipal flows (if it didn't, it could be a security issue, which is why APIs that don't flow ExecutionContext always end in Unsafe).

In UI apps, culture doesn't flow, but by default it's the same for all threads anyway. Name is definitely not going to flow, unless you resume on the same thread (e.g., using a UI SynchronizationContext).


For some further reading, I recommend starting with my own async / await tutorial and then the official async / await FAQ. Then take a look at Stephen Toub's blog post on ExecutionContext vs. SynchronizationContext.

You may also find my SynchronizationContext article helpful.

Crossover answered 17/9, 2012 at 13:23 Comment(3)
Thanks. The link to Stephen Toub's blog post on ExecutionContext vs. SynchronizationContext was extremely helpful to me.Encephalon
Dude you are all over async. Like everywhere. Nice!Colloidal
The link to Stephen Toub's post is giving a 403 but I also found it here devblogs.microsoft.com/pfxteam/…Watusi
P
4

No, the async/await keywords have everything to do with concurrency. async/await basically wrap your method code in to a task and continuation. To see the exact translation that the compiler produces (using the Task Parallel Library) disassemble some code snippet. This translation of async/await usage is 'similar' (but not identical!) to the example below

async Task<int> TaskOfTResult_MethodAsync()
{
    int hours;
    // . . .
    // Return statement specifies an integer result.
    return hours;
}

// Calls to TaskOfTResult_MethodAsync
Task<int> returnedTaskTResult = TaskOfTResult_MethodAsync();
int intResult = await returnedTaskTResult;
// or, in a single statement
int intResult = await TaskOfTResult_MethodAsync();

this is approxiamtely converted to

private int Result()
{
    int hours;
    // . . .
    // Return statement specifies an integer result.
    return hours;
}

where you wait for the return outside of the method like

int? hours = null;
Task<int> task = null;
task = Task.Factory.StartNew<int>(() => Result());
task.ContnueWith(cont => 
{
    // Some task completion checking...
    hours = task.Result;
}, CancellationToken.None, 
   TaskCreationOptions.None, 
   TaskScheduler.Current);

Or, you could place the TPL code into the Result method

private int ResultAsync()
{
    int? hours = null;
    Task<int> task = null;
    task = Task.Factory.StartNew<int>(() => 
    {
        int hours;
        // . . .
        // Return statement specifies an integer result.
        return hours;
    }, CancellationToken.None, 
       TaskCreationOptions.None, 
       TaskScheduler.Current);
    try
    {
        return task.Result;
    }
    catch (AggregateException aggEx)
    {
        // Some handler method for the agg exception.
        aggEx.Handle(HandleException); 
    }
}

SynchronizationContext does not guarantee that continuation will be executed on the same thread for the async/awate code. However, you can set the context using TPL code, via the SynchronisationContex keyword.

Predicable answered 17/9, 2012 at 10:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.