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 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- IAsyncEnumerable과 await foreach
- System.Threading.Channels
- ValueTask와 할당 방지
- ConfigureAwait와 동기화 컨텍스트