0Pricing
C# Academy · 강의

IAsyncEnumerable과 await foreach

IAsyncEnumerable으로 데이터를 비동기 스트리밍하고 비동기 반복기에서 yield를 사용하며 await foreach로 소비합니다.

IAsyncEnumerable과 await foreach은(는) CoddyKit의 무료 C# Academy 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 C# Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

IAsyncEnumerable이 필요한 이유

IAsyncEnumerable<T>(C# 8 및 .NET Core 3에서 도입됨)은 비동기 스트리밍을 지원합니다. 모든 항목을 먼저 메모리에 로드하지 않고, 항목을 사용할 수 있게 되는 대로 하나씩 내보냅니다. 데이터베이스 커서에서 행을 하나씩 읽거나 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()을 사용한 쿼리 결과 스트리밍을 지원합니다. 전체 결과 집합을 메모리에 로드하지 않고 행을 하나씩 읽으므로, 대규모 테이블에서 매우 중요합니다.

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을 팬인 지점으로 사용합니다. 여러 생산자가 쓰고 하나의 소비자가 읽도록 구성할 수 있습니다.

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()) 모든 데이터가 동시에 메모리에 유지되지만, 스트리밍은 한 번에 하나의 항목만 처리합니다.

// 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<T>를 반환하는 것과 비교할 때 IAsyncEnumerable의 가장 큰 메모리 이점은 무엇인가요?

복습: IAsyncEnumerable과 await foreach

핵심 요점:

  • IAsyncEnumerable<T>: 항목을 한 번에 하나씩 비동기적으로 스트리밍합니다
  • 비동기 메서드에서 yield return을 사용하면 비동기 반복자가 생성됩니다
  • await foreach: .WithCancellation()을 통해 취소를 지원하며 스트림을 소비합니다
  • [EnumeratorCancellation]: 소비자의 토큰을 반복자에게 전달합니다
  • EF Core: 대규모 쿼리를 메모리 효율적으로 처리하려면 AsAsyncEnumerable()을 사용합니다
  • 대규모 데이터 집합을 처리할 때는 ToListAsync() 대신 스트리밍합니다

자주 묻는 질문

“IAsyncEnumerable과 await foreach” 강의는 무료인가요?

네 — “IAsyncEnumerable과 await foreach” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C# Academy 강의 전체를 잠금 해제할 수 있습니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“IAsyncEnumerable과 await foreach”에서 뭘 배우나요?

IAsyncEnumerable으로 데이터를 비동기 스트리밍하고 비동기 반복기에서 yield를 사용하며 await foreach로 소비합니다. 브라우저에서 직접 실행하는 실습 코드로 C# Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

C# Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 C# Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.

“IAsyncEnumerable과 await foreach” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 C# Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 C# Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. IAsyncEnumerable과 await foreach
  2. System.Threading.Channels
  3. ValueTask와 할당 방지
  4. ConfigureAwait와 동기화 컨텍스트
← C# Academy(으)로 돌아가기