C# : 学びのメモ (1)  List と スレッドスタベーション

C# を書いているときに、記録に残しておこうと思ったので個人的なメモとして書きます。ポリシーとしては自分が 検索エンジンに頼ったり、レビューを受けて学びを得たことを検証して理解して、次回からスピードアップできることを意図とししています。

* Pull Request [APIs to enumerate all orchestration instances](https://github.com/Azure/azure-functions-durable-extension/pull/323)

# 検証1. List のコンストラクタ指定

Chris がレビューで、List のコンストラクタの数字は指定すること。指定しない場合、毎回領域を拡張するから遅いとのこと。実際コードを見てみる。

* [List の該当部分](https://github.com/dotnet/coreclr/blob/85374ceaed177f71472cc4c23c69daf7402e5048/src/System.Private.CoreLib/shared/System/Collections/Generic/List.cs#L229)

確かにそのとおりだ。デフォルトは empty の配列になっている。実際ためしてみよう。

```csharp
            var number = 100000000;
            // Add 10000 instances to the List. 
            var watch = new Stopwatch();
            watch.Start();

            var list = new List<SomeClass>(number);
            list.Add(
                new SomeClass
                {
                    Name = "ushio",
                    Age = 47
                }
                );
            watch.Stop();
            Console.WriteLine($"List With number constructor: {watch.ElapsedMilliseconds}");
            watch.Restart();
            list = new List<SomeClass>(number);
            list.Add(
                new SomeClass
                {
                    Name = "ushio",
                    Age = 47
                });
            watch.Stop();
            Console.WriteLine($"List without constructor: {watch.ElapsedMilliseconds}");
```

こんな感じでテストを実施。結果は以下の通り。小さいとそこまでインパクトありませんが、これぐらい回すと差が明確になるようです。

```
List With number constructor: 3
List without constructor: 1294
```

# スレッドのスタベーション

あるasyncメソッドがあり、そのメソッドの実態はフラグが on の時だけ内部で async コールをするのだが、実体は型変換というメソッドがありました。わたしはその部分を読んでいなかったので単純に、`Task.WhenAll` で並列処理させて早く終わらせようと考えました。このメソッドは1000程度のデータは来ることが想定されるのですが、彼から、メソッドスタベーションがおこるのでは？と指摘をうけました。結局のところ、リファクタリングして、型変換だけのメソッド抽出を行って解決したのですが、スレッドスタベーションが起こるかを試したくなりました。

```csharp
        public static async Task RunoutThreading()
        {
            var watch = new Stopwatch();
            watch.Start();
            var number = 100000;
            var list = new List<Task>(number);
            for(var i = 0; i < number; i++)
            {
                list.Add(SomeExecAsync());
            }
            watch.Stop();
            Console.WriteLine($"RunoutThread finished: elapse : {watch.ElapsedMilliseconds}");
            await Task.WhenAll(list);
        }
```

これぐらいの並列処理でもさして時間かからず終了しました。

```
18346
```

これぐらいの並列でもスレッドスタベーションは起こりません。この理由ですが、呼ばれる対象のメソッドがこんな感じです。`async/await`は賢いので、別スレッドを大量に作るとかはしないようです。本件はですので杞憂なのですが、よい知識の確認になりました。（むしろリファクタリングの問題w)

```csharp
        private static async Task SomeExecAsync()
        {
            Console.WriteLine($"SomeExecAsync(): Current: {Thread.CurrentThread.ManagedThreadId}");
            await Task.Delay(1000 * 3);
        }

```
結果

```
SomeExecAsync(): Current: 1
SomeExecAsync(): Current: 1
SomeExecAsync(): Current: 1
SomeExecAsync(): Current: 1
SomeExecAsync(): Current: 1
SomeExecAsync(): Current: 1
```

# まとめ

そういえば過去にブログ書いてた

* [マルチスレッドの問題を理解する](https://qiita.com/TsuyoshiUshio@github/items/58411209014a0fdfdd08)

## リソース

* [今回のサンプル](https://github.com/TsuyoshiUshio/ListSpike)
* [dotnet/coreclr: coreclr/src/System.Private.CoreLib/shared/System/Collections/Generic/List.cs](https://github.com/dotnet/coreclr/blob/85374ceaed177f71472cc4c23c69daf7402e5048/src/System.Private.CoreLib/shared/System/Collections/Generic/List.cs#L229)