8

DotNet Fiddle link https://dotnetfiddle.net/GqA32R

I have the following sample code to demonstrate the async local functionality

static AsyncLocal<string> _asyncLocalString = new AsyncLocal<string>();
    public static async Task Main()
    {
        // _asyncLocalString.Value = "Value 1";
        var t1 = AsyncMethodA();
        var t2 = AsyncMethodB();
        await Task.WhenAny(t1, t2).ConfigureAwait(false);
        Console.WriteLine("Finished");
        Console.WriteLine("Async local inside Main: " + _asyncLocalString.Value); // null
    }

    static async Task AsyncMethodA()
    {
        // _asyncLocalString.Value = "Value 2";
        Console.WriteLine("Method A");
        Console.WriteLine("Async local inside A: " + _asyncLocalString.Value); // null
        await Task.Delay(200);
    }

static async Task AsyncMethodB()
    {
        _asyncLocalString.Value = "Value 3";
        Console.WriteLine("Method B");
        await AsyncMethodC().ConfigureAwait(false);
    }

    static async Task AsyncMethodC()
    {
        await Task.Delay(100);
        Console.WriteLine("Method C");
        Console.WriteLine("Async local inside C: " + _asyncLocalString.Value); // Value 3 is printed
    }

Output:

Method B
Method C
Async local inside C: Value 3
Method A
Async local inside A: 
Finished
Async local inside Main: 

Is there a way to propogate these changes up the call stack so that async local changes in B are visible in A and main?

My real world scenario is similar to what I have above - the asyncLocal is set only in methodB and we have a few statements up the call stack which logs that value differently.

user2133404
  • 1,816
  • 6
  • 34
  • 60

2 Answers2

7

Is there a way to propogate these changes up the call stack so that async local changes in B are visible in A and main?

No, not with AsyncLocal<T>. async methods set their value context to "copy-on-write", so if it's written to, a copy will be created. And the copies never "flow" upwards.

You will need to use some other mechanism to achieve whatever it is you're trying to do.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Any idea about what that "other mechanism" might be? I thought about something like a static global mapping of Thread.ManagedThreadId to the corresponding value, but the ManagedThreadId doesn't have to stay the same across an async program flow... so I fail to find an identifier for the current ExecutionContext which could be used. – BudBrot Mar 04 '22 at 15:06
  • @BudBrot: One approach is to make the `T` mutable (or having a mutable member), but I generally discourage that. Another approach is to create a unique identifier and have a global mapping of some kind from those to your values. – Stephen Cleary Mar 04 '22 at 15:21
  • 1
    @BudBrot just create some container class like this: `class Container { public T ContainedValue {get; set;} }` and then use it like this: `AsyncLocal>`. – AgentFire Aug 26 '22 at 15:51
2

Is there a way to propogate these changes up the call stack so that async local changes in B are visible in A and main?

You could wrap the instance for example with AsyncLocal<StrongBox<T>>. In this way the value will live inside the instance originally set by the parent.