c# async enumerable cancellationtoken 사용법: 실무 적용 가이드
c# async enumerable cancellationtoken 사용법을 실무 관점에서 정리합니다. 개념, 코드 예제, 취소 패턴, 성능·운영 주의점과 체크리스트까지 바로 적용 가능한 가이드를 제공합니다.
c# async enumerable cancellationtoken 사용법이 필요한 상황
c# async enumerable cancellationtoken 사용법을 통해 비동기 스트리밍 작업에서 중단(취소)을 안정적으로 처리하는 방법을 배웁니다. 이 글에서는 IAsyncEnumerable의 기본 개념, CancellationToken을 안전하게 전달하는 패턴, 실제 코드 예제와 운영 시 주의점 및 체크리스트를 제공해 실무에 바로 적용할 수 있도록 구성했습니다.
c# async enumerable cancellationtoken 사용법 핵심 개념
IAsyncEnumerable<T>는 비동기적으로 시퀀스 항목을 생성하고 소비할 때 사용합니다. await foreach를 통해 항목을 소비하며, 긴 처리나 무한 스트림에서 CancellationToken을 전달해 중단시킬 수 있어야 합니다. 취소 토큰을 무시하면 리소스 누수, 오래 대기하는 작업, 사용자 경험 저하가 발생합니다.
CancellationToken 전달 패턴과 권장 방식
일반적인 권장 방식은 생산자(메서드 정의자)가 CancellationToken을 파라미터로 받고, 내부에서 필요시 토큰을 확인하거나 OperationCanceledException을 발생시키는 것입니다. 소비자는 자신의 토큰을 await foreach에 전달합니다.
c# async enumerable cancellationtoken 사용법 실무 예제
아래 예제는 서버에서 이벤트 스트림을 생성하고 클라이언트 요청 취소 시 스트리밍을 중단하는 패턴입니다.
using System; using System.Collections.Generic; using System.Threading; public class EventProducer { public async IAsyncEnumerable<int> GenerateAsync([System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken) { for (int i = 0; i < 1000; i++) { // 취소 요청 시 즉시 종료 cancellationToken.ThrowIfCancellationRequested(); await Task.Delay(100, cancellationToken); // 일부 작업에서 토큰 전달 yield return i; } } } // 소비자 쪽 public async Task ConsumeAsync(CancellationToken cancellationToken) { var producer = new EventProducer(); await foreach (var item in producer.GenerateAsync(cancellationToken).WithCancellation(cancellationToken)) { Console.WriteLine(item); // 조건에 따라 소비자 측에서도 취소할 수 있음 } }
주의: C# 컴파일러 버전 및 메타데이터에 따라 EnumeratorCancellationAttribute([EnumeratorCancellation])를 사용해 await foreach에서 토큰을 전달할 수 있습니다. .NET 5+ 권장.
언제 쓰면 좋고 언제 피해야 할까
언제 쓰면 좋나
- 서버에서 클라이언트로 대량의 데이터를 스트리밍할 때
- 실시간 이벤트 피드, 로그/메트릭 스트리밍
- 데이터를 점진적으로 처리해야 할 때(메모리 절약)
언제 피해야 하나
- 항목 수가 매우 적고 단일 배치로 처리하는 것이 더 간단한 경우
- 매우 짧은 시간의 고성능 루프(오버헤드가 부담될 때)
- 호출 스택에서 CancellationToken을 전달하기 어려운 라이브러리 경계
비교 표: IAsyncEnumerable vs Channel vs Push 기반 이벤트
| 구분 | 항목1 | 항목2 | 항목3 |
|---|---|---|---|
| 내용 | IAsyncEnumerable | Channel | Push 이벤트 |
| 내용 | 소비자가 pull 방식으로 항목을 가져옴, 자연스러운 backpressure | 생산자/소비자 분리, 버퍼링 제어 용이 | 외부 콜백, 구독/해제 필요 |
| 내용 | CancellationToken 전달로 취소 용이 | CancellationToken으로 Stop 시나리오 구현 가능 | 별도 취소 관리 필요 |
실무 체크리스트
- CancellationToken을 메서드 시그니처에 명시했는가?
- await foreach에서 WithCancellation이나 EnumeratorCancellation을 사용했는가?
- 내부 비동기 호출(Task.Delay, HttpClient 등)에 토큰을 전달했는가?
- long-running 루프에 ThrowIfCancellationRequested 호출이 있는가?
- 예외(특히 OperationCanceledException)를 적절히 처리하고 로그를 남기는가?
성능·보안·운영 주의점
- 성능: IAsyncEnumerable의 각 yield는 상태 머신 오버헤드가 있으며 매우 짧은 연산에서 오히려 비용이 클 수 있음. 고빈도 짧은 작업에는 배치 처리 고려.
- 보안: 스트리밍 도중 인증/권한이 변경될 수 있으니, 항목 노출 전에 권한 검사를 반복적으로 수행하거나 토큰 기반 접근 제어를 채택.
- 운영: 클라이언트 취소(네트워크 끊김 등) 시 자원(예: DB 커넥션, 파일 핸들)을 즉시 해제할 로직 필요. 취소 로그를 통해 누수 탐지.
구현 시 흔한 실수와 해결 방법
- 토큰을 전달하지 않음 -> WithCancellation 또는 EnumeratorCancellation 사용
- 내부 비동기 API에 토큰 미전달 -> Task.Delay/HttpClient에 token 전달
- 취소 시 리소스 해제 미흡 -> finally 블록에서 Dispose/Close 호출
자주 묻는 질문
Q1: await foreach에 CancellationToken을 직접 넣을 수 있나요? A1: C# 8+에서는 await foreach(var x in source.WithCancellation(token)) 형태를 사용하거나, 생산자쪽 메서드 시그니처에 [EnumeratorCancellation] CancellationToken을 받아야 안전하게 전달됩니다.
Q2: OperationCanceledException을 잡아야 하나요? A2: 네. 취소는 예외 형태로 전파될 수 있으므로 상위에서 OperationCanceledException을 포착하여 정상 종료로 처리하고 불필요한 오류 플래그가 남지 않도록 해야 합니다.
Q3: IAsyncEnumerable에서 리소스 해제는 어떻게 처리하나요? A3: yield 후 리소스 해제가 필요하면 try/finally 블록을 사용하거나 IAsyncDisposable 패턴을 구현해 소비자가 await using으로 처리하게 합니다.
Q4: CancellationToken이 없는 라이브러리와 결합할 때는? A4: 외부 호출을 래핑해 타임아웃/취소 로직을 추가하거나 CancellationTokenSource.CancelAfter를 사용해 안전망을 둡니다.
정리
- IAsyncEnumerable와 CancellationToken을 함께 쓰면 비동기 스트리밍 취소를 깔끔하게 처리할 수 있다.
- 생산자(메서드)에서 토큰을 받고 EnumeratorCancellation 또는 WithCancellation을 통해 전달하라.
- 내부 비동기 호출에도 토큰을 전달하고, 취소 시 리소스 해제를 반드시 처리하라.
- 성능 이슈를 고려해 매우 짧은 작업은 배치화하거나 대체 패턴을 검토하라.
- 실무 체크리스트를 통해 취소 로직 누락을 방지하라.