IAsyncEnumerableとawait foreach
IAsyncEnumerableでデータを非同期にストリーミングし、非同期イテレーターでyieldを使い、await foreachで消費します。
「IAsyncEnumerableとawait foreach」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。
なぜIAsyncEnumerableを使うのか
IAsyncEnumerable<T>(C# 8および.NET Core 3で導入)は、非同期ストリーミングを可能にします。すべての項目を先にメモリへ読み込むのではなく、利用可能になった項目を1つずつ返します。データベースカーソルの行を1行ずつ読み取ったり、APIレスポンスをストリーミングしたりする場合を考えてみてください。
非同期イテレーターの記述
IAsyncEnumerable<T>を返すメソッドを宣言し、非同期メソッド内でyield returnを使用します。awaitとyield returnのキーワードは自然に共存できます。
public async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
{
for (int i = 0; i < count; i++)
{
await Task.Delay(100); // simulate async work per item
yield return i;
}
}await foreachによる消費
await foreachを使ってIAsyncEnumerable<T>を消費します。各反復では、次の項目が届くまで非同期に待機するため、コンシューマーとプロデューサーの処理速度が自然に調整されます。
await foreach (var number in GenerateNumbersAsync(10))
{
Console.WriteLine(number);
}
// With cancellation:
var cts = new CancellationTokenSource();
await foreach (var n in GenerateNumbersAsync(100)
.WithCancellation(cts.Token))
{
if (n > 50) { cts.Cancel(); break; }
Console.WriteLine(n);
}データベースからのストリーミング
EF Coreでは、AsAsyncEnumerable()を使ってクエリ結果をストリーミングできます。完全な結果セットをメモリに読み込まず、行を1行ずつ読み取るため、大きなテーブルでは特に重要です。
public async IAsyncEnumerable<Product> StreamProductsAsync(
[EnumeratorCancellation] CancellationToken ct = default)
{
await foreach (var product in _db.Products
.Where(p => p.IsActive)
.AsAsyncEnumerable()
.WithCancellation(ct))
{
yield return product;
}
}HTTPレスポンスのストリーミング
ペイロード全体をバッファーに保持せず、大きなレスポンスをクライアントへストリーミングします。サーバーが処理を続けている間も、クライアントはデータを段階的に受け取れます。
app.MapGet("/stream/products", (ProductRepository repo) =>
{
// Returns an IAsyncEnumerable — ASP.NET Core streams it as JSON array
return repo.StreamAllAsync();
});
// Or with custom serialization:
app.MapGet("/stream/events", async (HttpResponse response, EventStore store) =>
{
response.ContentType = "application/json";
await foreach (var e in store.StreamAsync())
{
await response.WriteAsJsonAsync(e);
await response.Body.FlushAsync();
}
});EnumeratorCancellation属性
非同期イテレーターのCancellationTokenパラメーターに[EnumeratorCancellation]を適用すると、コンシューマー側のWithCancellation()からトークンがプロデューサーへ渡されます。
public async IAsyncEnumerable<LogEntry> TailLogAsync(
string logFile,
[EnumeratorCancellation] CancellationToken ct = default)
{
while (!ct.IsCancellationRequested)
{
var newLines = await ReadNewLinesAsync(logFile, ct);
foreach (var line in newLines)
yield return new LogEntry(line);
await Task.Delay(500, ct);
}
}
// Consumer:
await foreach (var entry in TailLogAsync("/var/log/app.log")
.WithCancellation(cts.Token))
{
Console.WriteLine(entry.Message);
}IAsyncEnumerableに対するLINQ
System.Linq.Async NuGetパッケージは、IAsyncEnumerable向けの非同期LINQ演算子(WhereAwait、SelectAwait、ToListAsync)を提供します。
// dotnet add package System.Linq.Async
var expensiveProducts = await repo.StreamAllAsync()
.WhereAwait(async p => await pricing.IsExpensiveAsync(p.Id))
.SelectAwait(async p => await enricher.EnrichAsync(p))
.Take(10)
.ToListAsync();
// Or use built-in Where/Select that don't need async:
var names = repo.StreamAllAsync()
.Where(p => p.IsActive)
.Select(p => p.Name);複数の非同期ストリームのマージ
複数の非同期ストリームを並行して処理するには、ファンインポイントとしてChannelを使用します。複数のプロデューサーから書き込み、1つのコンシューマーで読み取ります。
async IAsyncEnumerable<T> Merge<T>(
params IAsyncEnumerable<T>[] streams)
{
var channel = Channel.CreateUnbounded<T>();
var producers = streams.Select(async stream =>
{
await foreach (var item in stream)
await channel.Writer.WriteAsync(item);
});
_ = Task.WhenAll(producers)
.ContinueWith(_ => channel.Writer.Complete());
await foreach (var item in channel.Reader.ReadAllAsync())
yield return item;
}パフォーマンス:バッファリングとストリーミング
大規模なデータセットを処理する場合は、常にストリーミングを使用します。最初にすべてをリストへ読み込む(ToListAsync())と、全データを同時にメモリ上に保持することになります。ストリーミングなら、項目を1つずつ処理できます。
// BAD: loads all 1M records into memory
var allOrders = await _db.Orders.ToListAsync();
foreach (var order in allOrders)
await ProcessAsync(order);
// GOOD: processes one record at a time
await foreach (var order in _db.Orders.AsAsyncEnumerable())
await ProcessAsync(order);
// Memory usage: O(1) vs O(n)非同期イテレーターでのエラー処理
プロデューサーからのエラーを処理するには、await foreach全体をtry/catchで囲みます。イテレーター内のエラーは、現在の反復位置でコンシューマーへ伝播します。
try
{
await foreach (var item in RiskyStreamAsync())
{
await ProcessAsync(item);
}
}
catch (HttpRequestException ex)
{
Console.Error.WriteLine($"Stream error: {ex.Message}");
}
catch (OperationCanceledException)
{
Console.WriteLine("Stream cancelled.");
}確認問題
List
まとめ:IAsyncEnumerableとawait foreach
重要なポイント:
IAsyncEnumerable<T>:項目を非同期に1つずつストリーミングします- 非同期メソッド内の
yield returnで、非同期イテレーターを生成します await foreach:.WithCancellation()によるキャンセルに対応してストリームを消費します[EnumeratorCancellation]:コンシューマーのトークンをイテレーターへ伝播します- EF Core:大規模なクエリをメモリ効率よく処理するには
AsAsyncEnumerable()を使用します - 大規模なデータセットの処理では、
ToListAsync()ではなくストリーミングを使用します
よくある質問
「IAsyncEnumerableとawait foreach」レッスンは無料ですか?
はい。「IAsyncEnumerableとawait foreach」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。
「IAsyncEnumerableとawait foreach」で何を学びますか?
IAsyncEnumerableでデータを非同期にストリーミングし、非同期イテレーターでyieldを使い、await foreachで消費します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C# Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。
「IAsyncEnumerableとawait foreach」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC# Academyレッスンでコードを書いて実行できますか?
はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- IAsyncEnumerableとawait foreach
- System.Threading.Channels
- ValueTaskとアロケーションの回避
- ConfigureAwaitとSynchronizationContext