2

I've the following example

    [TestMethod]
    public void AsyncLocalFlowsInContinuation()
    {
        AsyncLocal<int> local = new AsyncLocal<int>();

        var task1 = Task.Run(() =>
        {
            local.Value = 1;
        });

        local.Value.Should().Be(0);                       

        var t1c = task1.ContinueWith((r) =>
        {
            local.Value.Should().Be(1); //THROW: it is 0 instead
        });

        local.Value.Should().Be(0);

        t1c.Wait();
    }

Isn't this supposed to work out of the box? in my understanding AsyncLocal should act like a static but specific for each task (in fact works for child task created inside "task1", for example) but instead it doesn't appear to have the same value in the continuation task "t1c". Should I specify something to allow the ExecutionContext to flow correctly inside the continuation task? or am i missing something obvuois?

i'm targetting .Net Standard 2.0 it is not mandatory to use AsyncLocal here, if it is the wrong type for this usage i'll be happy to use another one as long as the test's semantic will stay the same.

Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76
Mosè Bottacini
  • 4,016
  • 2
  • 24
  • 28
  • `Task.Run` doesn't participate in `async` / `await` the way you think it does. It existed previously, as part of the TPL, and is just an shortcut to spinning up a small bit of work on a background thread. `AsyncLocal` just crosses async context boundaries between `await` calls. It doesn't cross thread boundaries on its own. – Bradley Uffner Mar 05 '18 at 14:56
  • Thanks Bradley, so can you suggest a way to transport a context value set in a task to its continuation tasks? it is really something "static like", not something i want to transport in the task result as it has nothing to do with the transformation the tasks in the chain does. Imagine it as "the user who is performing this chain of tasks". – Mosè Bottacini Mar 05 '18 at 15:18
  • 1
    You might find this article interesting: https://weblogs.asp.net/dixin/understanding-c-sharp-async-await-3-runtime-context. In "Marshal to execution context" part you can find some useful (probably) methods for your situation, like `ContinueWithContext`. – Evk Mar 05 '18 at 15:37
  • Have you tried the Logical CallContext? It looks like it has the stack-like behavior your want. https://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html – Bradley Uffner Mar 05 '18 at 17:30
  • Hi Bradley, CallContext looks very promising but i'm targeting .NET Standard 2.0 and doesn't seems to be supported at the moment. maybe i'll try to mimic the implementation. thanks! – Mosè Bottacini Mar 06 '18 at 08:30

2 Answers2

1

Nearly 3 years late, but anyway... this is happening because Task continuations are not nested tasks of the Task they continue. They execute within the context of the caller of the antecedent task. That is, if your unit test was an async test, for example, the continuation would be a nested Task of the test method itself, and could access it's async local state, if any.

This doesn't change whether the continuation returns Task or not.

However, as an important note, if the continuation did return Task, such as if it used Task.Run and returned its Task result, there is a 'trick' to getting it to behave correctly:

It must either be an async lambda, and await the Task within itself, or use Unwrap()

See https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/chaining-tasks-by-using-continuation-tasks#continuations-that-return-task-types

DvS
  • 1,025
  • 6
  • 11
-1

The tasks are running normally and according to the intended execution flow. But, the problem is with the AsyncLocal<T> class, which is used to

Represent ambient data that is local to a given asynchronous control flow, such as an asynchronous method.

In other words, the data assigned to the local variable is local to the thread context it is used in. Thus, there are three different "versions" of the variable: one for the main thread, another one for the context of the first task, and the latest for the continuation task. Each will have a different value.

If you used a normal integer data type (int), you will get the expected values.

Please, refer to these links for more information:

https://msdn.microsoft.com/en-us/library/dn906268(v=vs.110).aspx

Safety of AsyncLocal in ASP.NET Core

Oday Fraiwan
  • 1,147
  • 1
  • 9
  • 21
  • Thanks Oday, The example in the test is a simplified version of my code which is heavily task based, and represent the creation of a task (initiator) in a part of the code and the appending of multiple continuation task in another part. The continuation tasks shares a context value with the initiator, which they should read. There will be many of these task "chain" running in parallel, the initiator will set a context value and only its continuation tasks will read it. This context is different from the task result. it is really something static which flows in continuation,not a transformation – Mosè Bottacini Mar 05 '18 at 15:13