はじめに
C# / .NET を深堀していくシリーズを始めます。
Deepと書きつつも一度に多くを扱わずに、1つあたり数分で理解できる軽快なものを書き連ねてゆく予定です。
第1回は await foreach
です。
従来のforeachと何が異なるのか
まずはこのコードをご覧ください。C#8.0から採用された await foreach
は従来の foreach
と何が異なるでしょうか。
コードを比較しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// C#8.0で追加された await foreach を使う例 IAsyncEnumerable<int> asyncItems = Enumerable.Range(0, 100).ToAsyncEnumerable(); await foreach(var item in asyncItems) // ここを非同期化できるようになった { Console.WriteLine(item); } // C#7.x以前のコード IEnumerable<int> items = Enumerable.Range(0, 100); foreach(var item in items) // この部分を非同期処理化できない { Console.WriteLine(item); } |
列挙対象の型が異なります。そこがポイントです。
そのおかげで列挙処理自体を async/await で実装することが出来るようになりました。
これらのコードには多少のバリエーションはありますが、多くの場合以下のように展開されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// await foreach は以下のように展開します。 await using(IAsyncEnumerator<int> asyncEnumerator = asyncItems.GetAsyncEnumerator()) { while(await asyncEnumerator.MoveNextAsync()) { int item = asyncEnumerator.Current; Console.WriteLine(item); } } // foreach の展開例です。IDisposableを実装している場合は以下のようになります。 using(IEnumerator<int> enumerator = items.GetEnumerator()) { while(enumerator.MoveNext()) { int item = enumerator.Current; Console.WriteLine(item); } } |
await asyncEnumerator.MoveNextAsync()
の部分が非同期化しました。
これは、従来なら enumerator.MoveNext()
が同期処理であるためにブロッキングする可能性があったところを回避できるようになったことを意味しています。
厳密なところではGetAsyncEnumerator()
の結果に対するnull判定などが含まれますがここでは割愛します。
その件はこの Deep C# で using/await using
の解析をする時まで取っておきます。
yield return との相性
C#7.xまでは yield return
を非同期化することはできませんでした。
これは比較的単純な話で yield return
を用いるメソッドが IAsyncEnumerable<T>
に対応していなかったためです。
C#8.0以降は yield return / async / await
を混在することが可能になりました。
使いどころ
await foreach
の使いどころはすでにいくつか考えられます。
- ファイルI/O(FileStreamなど)
- ネットワークアクセス(HttpClientなど)
- シリアライズ/デシリアライズ(JsonSerializerなど)
- データベースアクセス(EF Coreなど)
これらの多くには、すでに実際に末尾に Async と付いたメソッドが追加されています。
C#7.xまでは、このいかにも foreach
で記述したいところに非同期処理を挟むことが出来ませんでしたから、これは大変な進歩といえます。
また、上記のものを内包するラッパークラスなども大きな恩恵を受けることと思います。
まとめ
C#1.0から存在したforeach
が非同期対応したことの意味は大変大きなものです。
これは非同期処理では使えないC#の構文がほぼなくなったことを意味します。await foreach
をしっかりと活用したいですね。
第2回は record / record struct
を扱います。ご期待ください。