본문 바로가기
C.W.K.
Stream
Lesson 05 of 07 · published

비동기 반복 — 다음 값도 기다려야 할 때

~18 min · async, async-for, async-iter, asyncio

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

값 하나를 얻는 데 I/O가 필요할 수 있어

동기 반복자는 next가 바로 값을 주지만, 네트워크나 센서 흐름은 다음 값을 기다려야 해. 비동기 반복은 __aiter____anext__, async for로 그 기다림을 규약 안에 넣어.

비동기 제너레이터가 가장 간단해

async def 안에서 yield하면 비동기 제너레이터가 돼. 값을 내기 전에 await할 수 있고, 소비자는 비동기 for로 받아. 비동기 컴프리헨션으로 결과를 모을 수도 있어.

상태와 제어가 많으면 클래스로

재시도·설정·수명처럼 명시적 상태가 필요하면 __aiter__/__anext__를 가진 클래스를 쓸 수 있어. 끝에서는 StopAsyncIteration을 내야 하며, 동기 StopIteration과 섞으면 규약이 깨져.

원칙: 비동기 반복은 값을 흘려보내는 규약에 기다림을 더한 것이지, 자동 병렬 처리 장치가 아니야.

Code

간단한 비동기 제너레이터·python
import asyncio

async def ticker(n):
    for i in range(n):
        await asyncio.sleep(0.1)        # I/O 좀
        yield i

async def main():
    async for x in ticker(3):
        print(x)
# 0
# 1
# 2  (각 ~100ms 간격)

asyncio.run(main())
비동기 for로 값 소비하기·python
import asyncio

async def chunks():
    for piece in ["hello ", "world ", "from ", "pippa"]:
        await asyncio.sleep(0.05)
        yield piece

async def collect():
    full = ""
    async for chunk in chunks():
        full += chunk
    return full

result = asyncio.run(collect())
print(result)                   # 'hello world from pippa'
비동기 컴프리헨션으로 모으기·python
import asyncio

async def naturals(limit):
    for n in range(1, limit + 1):
        await asyncio.sleep(0.01)
        yield n

async def main():
    # async list 컴프리헨션
    squares = [x*x async for x in naturals(5)]
    print(squares)              # [1, 4, 9, 16, 25]

    # async generator expression
    total = sum([x*x async for x in naturals(5)])
    print(total)                # 55

asyncio.run(main())
상태가 필요한 비동기 반복자 클래스·python
import asyncio

class DelayedRange:
    def __init__(self, n, delay=0.05):
        self.n = n
        self.delay = delay
        self.i = 0

    def __aiter__(self):
        return self

    async def __anext__(self):
        if self.i >= self.n:
            raise StopAsyncIteration
        await asyncio.sleep(self.delay)
        v = self.i
        self.i += 1
        return v

async def main():
    async for x in DelayedRange(4):
        print(x)

asyncio.run(main())
규약을 어겼을 때의 분명한 오류·python
import asyncio

async def main():
    # 일반 iterable 에 async for — 에러
    try:
        async for x in [1, 2, 3]:
            pass
    except TypeError as e:
        print(e)                # 'list' object is not async iterable

    # async iterable 에 for — 에러
    async def stream():
        yield 1

    try:
        for x in stream():
            pass
    except TypeError as e:
        print(e)                # 'async_generator' object is not iterable

asyncio.run(main())

External links

Exercise

0부터 count-1까지 내주되 각 값 사이에 await asyncio.sleep(every)를 하는 비동기 제너레이터 tick(every, count)를 만들어. 비동기 for로 값과 경과 시간을 출력한 뒤, 비동기 리스트 컴프리헨션으로 모두 모아 한꺼번에 출력하는 버전도 작성하고 asyncio.run으로 실행해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.