asyncio 는 cooperative 지 preemptive 아냐
async 함수는 await 지점에서만 컨트롤 양보. await 사이엔 CPU 가짐. 즉 async 는 I/O bound 작업 (API 호출, DB 쿼리, 파일 읽기) 에 좋고 CPU bound 작업 (loop 막음) 에 나쁨.
어디든 등장하는 세 패턴
gather: 여러 awaitable 동시 실행, 모두 wait. cwkPippa 에서 Family Council Mode 1 의 여러 두뇌에 fan out 할 때 사용.
create_task: awaitable 백그라운드로 시작, handle 받음. 작업 시작하되 아직 block 하기 싫을 때.
async 제너레이터: async def + yield 함수. 각 yield 가 awaitable. SSE event stream 이 정확히 이 패턴 — async for chunk in adapter.stream(...).
__anext__() 를 asyncio.wait_for 로 감싼 거. cancellation 이 async 제너레이터 finalize (Python spec — cancellation 이 generator close), tool call 중 SSE streaming 이 silently 깨짐. fix: __anext__ 를 wait_for 로 감싸지 마. 전체 episode 가 docs/COMMON-GOTCHAS.md #1 에 있어.
세 도구를 고르는 기준도 다르지. 결과가 전부 필요하면 gather, 지금 시작하되 lifecycle을 따로 관리할 이유가 있으면 create_task, 결과가 생기는 순서대로 흘려보내야 하면 async generator야. 특히 create_task는 fire-and-forget 주문이 아니야. handle을 잃으면 error도 cancellation도 누가 거둘지 사라져.
사실 concurrency의 어려움은 동시에 시작하는 데 있지 않아. 하나가 실패하거나 caller가 떠났을 때 나머지를 어떻게 정리할지에 있어. task를 만든 코드가 종료·취소·예외 수거까지 소유해야 해. 그 lifecycle을 설명할 수 없으면 빠른 코드가 아니라 떠돌이 작업을 만든 거야.