0Pricing
C# Academy · レッスン

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の主なメモリ上の利点は何ですか。

まとめ: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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. IAsyncEnumerableとawait foreach
  2. System.Threading.Channels
  3. ValueTaskとアロケーションの回避
  4. ConfigureAwaitとSynchronizationContext
← C# Academyに戻る