40. IAsyncEnumerable<T> / await foreach 의 상태 머신·취소·ConfigureAwait

난이도 상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// [주제] IAsyncEnumerable<T> / await foreach 의 상태 머신, 취소, ConfigureAwait
//
// 게임서버에서 대량 결과를 스트리밍(예: DB 커서, 존 엔티티 순회)할 때 쓰는
// 비동기 스트림이다. 아래 코드의 동작을 예측하고 내부 동작을 설명하라.
//
// [과제]
//  1) StreamScores 같은 메서드(yield return + await 혼합)를 컴파일러가
//     무엇으로 변환하는가? IEnumerator 상태 머신과 무엇이 다른가?
//     (MoveNextAsync 가 ValueTask<bool> 를 반환하는 이유 포함)
//  2) await foreach 는 어떤 인터페이스/메서드를 호출하며, 열거 종료 시
//     DisposeAsync 는 언제 불리는가? 소비자가 break 하면?
//  3) CancellationToken 을 [EnumeratorCancellation] 로 받는 것과
//     WithCancellation(token) 으로 넘기는 것의 관계를 설명하라. token 이
//     생성 시점(StreamScores 호출)과 열거 시작 시점 중 어디에 묶이는가?
//  4) ConfigureAwait(false) 를 await foreach 에 적용하면 무엇이 달라지는가?
//     생산자 내부의 await 에는 어떻게 전파되는가?

using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

public class ScoreStreamer
{
    public async IAsyncEnumerable<int> StreamScores(
        int total,
        [EnumeratorCancellation] CancellationToken ct = default)
    {
        for (int i = 0; i < total; i++)
        {
            ct.ThrowIfCancellationRequested();
            int page = await FetchPage(i, ct);   // 비동기 I/O
            yield return page;                    // 하나씩 산출
        }
    }

    private Task<int> FetchPage(int i, CancellationToken ct) => Task.FromResult(i * 10);

    public async Task<long> Consume(int total, CancellationToken external)
    {
        long sum = 0;
        await foreach (var s in StreamScores(total)
                                 .WithCancellation(external)
                                 .ConfigureAwait(false))
        {
            sum += s;
            if (sum > 1000) break;
        }
        return sum;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.