C# AsyncLocal の振る舞い

今貢献しているプロジェクトで、AsyncLocal のアドバンスドな使い方をする必要があるので、まずはなんとなく使っている AsyncLocal のリファレンスを読んで挙動を確認してみた。
 
* [AsyncLocal](https://docs.microsoft.com/en-us/dotnet/api/system.threading.asynclocal-1?view=netframework-4.7.2)

定義を読むと、Asyncのコントロールフロー（例えばAsyncメソッド）の中でローカルとして有効な限定された範囲内でのデータという感じ。読むだけではよくわからないので実験して定義を理解してみる。

# Code

内容は、AsyncLocal と ThreadLocal を使って、スレッドの挙動と共に振る舞いを調査しています。

* ExecuteAsync 
* Level1
* Level2

とそれぞれ AsyncLocal, ThreadLocal を更新してどのスコープで値がシェアされるかを確認しています。

```csharp
   class Program
    {

        private static AsyncLocal<string> asyncLocal = new AsyncLocal<string>();
        private static ThreadLocal<string> threadLocal = new ThreadLocal<string>();
        static void Main(string[] args)
        {
            var program = new Program();
            program.ExecuteAsync().GetAwaiter().GetResult();
            Console.ReadLine();
        }

        private async Task ExecuteAsync()
        {
            asyncLocal.Value = "Value 1";
            threadLocal.Value = "Value 1";
            var t1 = Level1("1");
            asyncLocal.Value = "Value 2";
            threadLocal.Value = "Value 2";
            var t2 = Level1("2");

            await t1;
            await t2;
            Console.WriteLine($"Level0: AsyncLocal: {asyncLocal.Value} Thread: {Thread.CurrentThread.ManagedThreadId}");
            Console.WriteLine($"Level0: ThreadLocal: {threadLocal.Value} Thread: {Thread.CurrentThread.ManagedThreadId}");
        }

        private async Task Level1(string value)
        {
            Console.WriteLine($"Level1:Start: {value} AsyncLocal: {asyncLocal.Value} ThreadLocal: {threadLocal.Value} Thread: {Thread.CurrentThread.ManagedThreadId}");
            asyncLocal.Value = $"{asyncLocal.Value}.{value}";
            threadLocal.Value = $"{threadLocal.Value}.{value}";
            // await Task.Delay(100);  // If you add wait, then, the it switch the Thread.
            await Level2(value);
            Console.WriteLine($"Level1:End: {value} AsyncLocal: {asyncLocal.Value} ThreadLocal: {threadLocal.Value} Thread: {Thread.CurrentThread.ManagedThreadId}");

        }

        private async Task Level2(string value)
        {
            Console.WriteLine($"Level2:Start: {value} AsyncLocal: {asyncLocal.Value} ThreadLocal: {threadLocal.Value} Thread: {Thread.CurrentThread.ManagedThreadId}");
            asyncLocal.Value = $"{asyncLocal.Value}.{value}";
            threadLocal.Value = $"{threadLocal.Value}.{value}";
            Console.WriteLine($"Level2:End: {value} AsyncLocal: {asyncLocal.Value} ThreadLocal: {threadLocal.Value} Thread: {Thread.CurrentThread.ManagedThreadId}");

        }
    }
```

# 実行結果 (Thread 1つ)

実行してみると、AsyncLocal と ThreadLocal で挙動が異なる。AsyncLocal はコールグラフの中で値がシェアされているが、Stack の形式で情報がシェアされるようだ。つまり、Asyncで呼び出した先のコールグラフで何かをAsyncLocal にセットしたとしても、呼び出し元のメソッドに戻るとそれは消えている。

一方、ThreadLocal は名前の通り、同じスレッド内だと値がシェアされる。だから、呼び出し先のメソッドで値を変更したら、呼び出し元が参照した、ThreadLocal の値は呼び出し先で変更されたものになる。

```
Level1:Start: 1 AsyncLocal: Value 1 ThreadLocal: Value 1 Thread: 1
Level2:Start: 1 AsyncLocal: Value 1.1 ThreadLocal: Value 1.1 Thread: 1
Level2:End: 1 AsyncLocal: Value 1.1.1 ThreadLocal: Value 1.1.1 Thread: 1
Level1:End: 1 AsyncLocal: Value 1.1 ThreadLocal: Value 1.1.1 Thread: 1
Level1:Start: 2 AsyncLocal: Value 2 ThreadLocal: Value 2 Thread: 1
Level2:Start: 2 AsyncLocal: Value 2.2 ThreadLocal: Value 2.2 Thread: 1
Level2:End: 2 AsyncLocal: Value 2.2.2 ThreadLocal: Value 2.2.2 Thread: 1
Level1:End: 2 AsyncLocal: Value 2.2 ThreadLocal: Value 2.2.2 Thread: 1
Level0: AsyncLocal: Value 2 Thread: 1
Level0: ThreadLocal: Value 2.2.2 Thread: 1
```

# 実行結果 (複数の Thread)

コードの中にある `await Task.Delay(100); ` をコメントアウトする。こうすることによって、スレッドがつかまれるので、複数のスレッドが利用される。スレッドが異なるので、ThreadLocal の場合は、値が共有されなくなっている。一方 AsyncLocal の方は値は全く同じだ。async/await 型の非同期処理の場合 Task にどのようにスレッドが割り当てられるかはわからないので、一般的には AsyncLocal を使うのがよさそう。もし、更新先での値を変更したい場合は、オブジェクトを共有したらいけるだろう。

```
Level1:Start: 1 AsyncLocal: Value 1 ThreadLocal: Value 1 Thread: 1
Level1:Start: 2 AsyncLocal: Value 2 ThreadLocal: Value 2 Thread: 1
Level2:Start: 1 AsyncLocal: Value 1.1 ThreadLocal:  Thread: 5
Level2:Start: 2 AsyncLocal: Value 2.2 ThreadLocal:  Thread: 4
Level2:End: 2 AsyncLocal: Value 2.2.2 ThreadLocal: .2 Thread: 4
Level2:End: 1 AsyncLocal: Value 1.1.1 ThreadLocal: .1 Thread: 5
Level1:End: 1 AsyncLocal: Value 1.1 ThreadLocal: .1 Thread: 5
Level1:End: 2 AsyncLocal: Value 2.2 ThreadLocal: .2 Thread: 4
Level0: AsyncLocal: Value 2 Thread: 4
Level0: ThreadLocal: .2 Thread: 4
```
# AsyncLocal をコールグラフ間で共有する

単純にオブジェクトの型にすると、共有してくれる。先ほどのプログラムに、オブジェクト参照で AsyncLocal をつかってみた。 予想通り。

```csharp
   class Context
    {
        public string Value { get; set; }
    }
    class Program
    {

        private static AsyncLocal<string> asyncLocal = new AsyncLocal<string>();
        private static AsyncLocal<Context> asyncLocalContext = new AsyncLocal<Context>();
        private static ThreadLocal<string> threadLocal = new ThreadLocal<string>();
        static void Main(string[] args)
        {
            var program = new Program();
            program.ExecuteAsync().GetAwaiter().GetResult();
            Console.ReadLine();
        }

        private async Task ExecuteAsync()
        {
            asyncLocal.Value = "Value 1";
            threadLocal.Value = "Value 1";
            asyncLocalContext.Value = new Context();
            asyncLocalContext.Value.Value = "Context Value 1";
            var t1 = Level1("1");
            asyncLocal.Value = "Value 2";
            threadLocal.Value = "Value 2";
            asyncLocalContext.Value = new Context();
            asyncLocalContext.Value.Value = "Context Value 2";
            var t2 = Level1("2");

            await t1;
            await t2;
            Console.WriteLine($"Level0: AsyncLocal: {asyncLocal.Value} AsyncLocalContext: {asyncLocalContext.Value.Value} Thread: {Thread.CurrentThread.ManagedThreadId}");
            Console.WriteLine($"Level0: ThreadLocal: {threadLocal.Value} Thread: {Thread.CurrentThread.ManagedThreadId}");
        }

        private async Task Level1(string value)
        {
            Console.WriteLine($"Level1:Start: {value} AsyncLocal: {asyncLocal.Value} ThreadLocal: {threadLocal.Value} AsyncLocalContext: {asyncLocalContext.Value.Value}  Thread: {Thread.CurrentThread.ManagedThreadId}");
            asyncLocal.Value = $"{asyncLocal.Value}.{value}";
            asyncLocalContext.Value.Value = $"{asyncLocal.Value}.{value}";
            threadLocal.Value = $"{threadLocal.Value}.{value}";
            await Task.Delay(100);  // If you add wait, then, the it switch the Thread.
            await Level2(value);
            Console.WriteLine($"Level1:End: {value} AsyncLocal: {asyncLocal.Value} ThreadLocal: {threadLocal.Value} AsyncLocalContext: {asyncLocalContext.Value.Value}  Thread: {Thread.CurrentThread.ManagedThreadId}");

        }

        private async Task Level2(string value)
        {
            Console.WriteLine($"Level2:Start: {value} AsyncLocal: {asyncLocal.Value} ThreadLocal: {threadLocal.Value} AsyncLocalContext: {asyncLocalContext.Value.Value}  Thread: {Thread.CurrentThread.ManagedThreadId}");
            asyncLocal.Value = $"{asyncLocal.Value}.{value}";
            threadLocal.Value = $"{threadLocal.Value}.{value}";
            asyncLocalContext.Value.Value = $"{asyncLocal.Value}.{value}";
            Console.WriteLine($"Level2:End: {value} AsyncLocal: {asyncLocal.Value} ThreadLocal: {threadLocal.Value} AsyncLocalContext: {asyncLocalContext.Value.Value}  Thread: {Thread.CurrentThread.ManagedThreadId}");

        }
    }
```

## 実行結果

```
Level1:Start: 1 AsyncLocal: Value 1 ThreadLocal: Value 1 AsyncLocalContext: Context Value 1  Thread: 1
Level1:Start: 2 AsyncLocal: Value 2 ThreadLocal: Value 2 AsyncLocalContext: Context Value 2  Thread: 1
Level2:Start: 1 AsyncLocal: Value 1.1 ThreadLocal:  AsyncLocalContext: Value 1.1.1  Thread: 5
Level2:Start: 2 AsyncLocal: Value 2.2 ThreadLocal:  AsyncLocalContext: Value 2.2.2  Thread: 4
Level2:End: 1 AsyncLocal: Value 1.1.1 ThreadLocal: .1 AsyncLocalContext: Value 1.1.1.1  Thread: 5
Level2:End: 2 AsyncLocal: Value 2.2.2 ThreadLocal: .2 AsyncLocalContext: Value 2.2.2.2  Thread: 4
Level1:End: 2 AsyncLocal: Value 2.2 ThreadLocal: .2 AsyncLocalContext: Value 2.2.2.2  Thread: 4
Level1:End: 1 AsyncLocal: Value 1.1 ThreadLocal: .1 AsyncLocalContext: Value 1.1.1.1  Thread: 5
Level0: AsyncLocal: Value 2 AsyncLocalContext: Value 2.2.2.2 Thread: 5
Level0: ThreadLocal: .1 Thread: 5
``` 
# まとめ

大体これで挙動が理解できたかと思う。

# Resource 

* [サンプルコード](https://github.com/TsuyoshiUshio/ActivitySpike)
* [async/awaitと同時実行制御](https://ufcpp.wordpress.com/2012/11/12/asyncawait%E3%81%A8%E5%90%8C%E6%99%82%E5%AE%9F%E8%A1%8C%E5%88%B6%E5%BE%A1/amp/)

# words

**Ambient adj**
(especially of environmental conditions) existing in the surrounding area:
Represents ambient data that is local to a given asynchronous control flow, such as an asynchronous method.