What does 'context' exactly mean in C# async/await code?
Asked Answered
C

2

32

Lets looks at some simple C# async/await code where I have an object reference (obj) before and after an await with ConfigureAwait(false)

private async Task<SomeObject> AnAsyncLibraryMethod(SomeObject obj)
{
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    obj.Name = "Harry"; // <-- obj here

    // MAIN POINT
    var newSubObj = await FetchOverHttpAsync().ConfigureAwait(false);

    // Continuation here
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    obj.Name = "Sally"; // <-- same obj here
    return obj;
}

public class SomeObject { public string Name; }

ConfigureAwait(false) seems to means NOT marshal the continuation back to the original context captured - ok, but what does that really mean? I've tried the code above and obj IS correctly referenced back (even when it resumes on a different thread).

So the "context" doesn't appear to be the thread's working memory (i.e. thread local storage). So what does "context" contain? Therefore, what does it really mean to

marshal the continuation back to the original context captured

Counterattack answered 25/3, 2015 at 22:51 Comment(0)
E
8

If I'm not totally wrong, ConfigureAwait(false); only means that the code which runs after the code you are awaiting, is not required to use the SynchronizationContext from before the await.

SynchronizationContext can be different things, as Stephen pointed out. Imagine you are in a web environment and your code after the await relies on HttpContext.Current.Items: this might not work anymore if you set ConfigureAwait(false);.

The following code in an MVC controller would throw an exception, for example:

public async Task<ActionResult> Index()
{
    System.Web.HttpContext.Current.Items["test"] = "test";

    var result = await SomethingAsync();
            
    return View();
}

private async Task<object> SomethingAsync()
{
    await Task.Delay(1000).ConfigureAwait(false);

    // this will throw a nullpointer if ConfigureAwait is set to false
    return System.Web.HttpContext.Current.Items["test"];
}

Your variable though is simply in the scope of the method and therefor it will be available, basically the method closure/scope, if this makes sense?

Eusporangiate answered 25/3, 2015 at 23:17 Comment(2)
"the code which runs after the code you are awaiting, must not run in the same SynchronizationContext." is incorrect. A better way to say it is "the code which runs after the code you are awaiting, Is not required to use the SynchronizationContext from before the await". If you await a task that is in the completed state you will likely still be using the same context as before the await because the code will have just gotten the result synchronously and never returned to the caller.Abacus
Makse sense, sorry I'm not native english so that was actually what I was trying to say. It is not always the case that the same sync context is not used because of what you said ;) Let me fix that phraseEusporangiate
F
9

As I describe in my async intro blog post, the "context" is:

  • SynchronizationContext.Current, unless it is null, in which case it is
  • TaskScheduler.Current. Note that if there is no current task scheduler, then TaskScheduler.Current is the same as TaskScheduler.Default, which is a thread pool context.

The vast majority of the time, this is either a UI or ASP.NET request context (both types of SynchronizationContext), or else it's the thread pool context. Task scheduler contexts seldomly come into play.

Note that this context is just used to schedule the continuation. It doesn't do anything with marshaling; in your example obj is captured just like it would be if it were referenced from a lambda expression.

Fickle answered 25/3, 2015 at 23:1 Comment(5)
Thanks, can you elaborate "just used to schedule the continuation."? Does it mean "run continuation WHEN the UI thread OR ASP.NET thread OR any thread from the thread pool (depending on context) is free again"?Counterattack
@Counterattack It means it calls SynchronizationContext.Send or SynchronizationContext.Post with the continuation delagate. It is up to the implementation of the context on how it processes that delegate (For the single threaded ones (WPF and Winforms) it is put it in to a message queue to be processed, for the pool based ones (ASP.net & thread pool) it grabs a resource from the pool.Abacus
In this example, what would happen to the fields of the object containing the AnAsyncLibraryMethod()? Are these also captured, or is some kind of barrier/lock generated?Intercession
@Denxorz: this is captured, and the fields hang off that. await does take care of inserting a memory barrier automatically if the resuming code runs on a different thread.Fickle
@Stephen: Thanks, that makes sense. So that means that the volatile from this answer is not needed?Intercession
E
8

If I'm not totally wrong, ConfigureAwait(false); only means that the code which runs after the code you are awaiting, is not required to use the SynchronizationContext from before the await.

SynchronizationContext can be different things, as Stephen pointed out. Imagine you are in a web environment and your code after the await relies on HttpContext.Current.Items: this might not work anymore if you set ConfigureAwait(false);.

The following code in an MVC controller would throw an exception, for example:

public async Task<ActionResult> Index()
{
    System.Web.HttpContext.Current.Items["test"] = "test";

    var result = await SomethingAsync();
            
    return View();
}

private async Task<object> SomethingAsync()
{
    await Task.Delay(1000).ConfigureAwait(false);

    // this will throw a nullpointer if ConfigureAwait is set to false
    return System.Web.HttpContext.Current.Items["test"];
}

Your variable though is simply in the scope of the method and therefor it will be available, basically the method closure/scope, if this makes sense?

Eusporangiate answered 25/3, 2015 at 23:17 Comment(2)
"the code which runs after the code you are awaiting, must not run in the same SynchronizationContext." is incorrect. A better way to say it is "the code which runs after the code you are awaiting, Is not required to use the SynchronizationContext from before the await". If you await a task that is in the completed state you will likely still be using the same context as before the await because the code will have just gotten the result synchronously and never returned to the caller.Abacus
Makse sense, sorry I'm not native english so that was actually what I was trying to say. It is not always the case that the same sync context is not used because of what you said ;) Let me fix that phraseEusporangiate

© 2022 - 2024 — McMap. All rights reserved.