8.9. ThreadSafeFlag

asyncio.ThreadSafeFlagEvent 가 지원하지 않는 경우, 즉 인터럽트 핸들러가 asyncio 태스크를 깨워야 하는 경우를 위해 asyncio가 제공하는 신호 프리미티브입니다. 인터럽트는 이벤트 루프의 정상적인 스케줄링 밖에서 발생하므로, asyncio.Event.set() 이 수행하는 관리 작업을 인터럽트 내부에서 안전하게 수행할 수 없습니다.

8.9.1. 별도의 프리미티브가 필요한 이유

asyncio.Event.set()루프가 현재 실행 중인 태스크 내부에서 호출되고 있다는 가정 하에 대기 중인 코루틴을 깨우는 관리 작업을 수행합니다. 인터럽트 핸들러는 그 가정을 충족하지 않습니다. 인터럽트는 임의의 두 명령어 사이에서 발생할 수 있어 루프가 진행 중이던 작업을 손상시킬 수 있습니다.

asyncio.ThreadSafeFlag.set() 은 그러한 컨텍스트에서 안전하도록 작성되었습니다. 이 안전성에 대한 대가로 Event 보다 기능 집합이 작습니다. ThreadSafeFlag 는 한 번에 하나의 태스크만 대기할 수 있으며, 대기 중인 태스크가 깨어나면 플래그가 자동으로 재설정됩니다.

8.9.2. 기본 형태

전형적인 사용 사례는 GPIO 인터럽트가 버튼 누름 이벤트를 asyncio 태스크에 넘기는 것입니다:

import asyncio
from machine import Pin

flag = asyncio.ThreadSafeFlag()

def on_press(pin):
    flag.set()

async def watcher():
    button = Pin("P1", Pin.IN, Pin.PULL_UP)
    button.irq(handler=on_press, trigger=Pin.IRQ_FALLING)
    while True:
        await flag.wait()
        print("button pressed")

asyncio.run(watcher())

on_press 는 GPIO가 발생할 때마다 인터럽트 컨텍스트에서 실행됩니다. 이것이 하는 일은 flag.set() 를 호출하는 것뿐입니다. asyncio 태스크 watcher 는 루프 안에서 flag.wait()await 합니다. 플래그가 설정될 때마다 태스크가 재개되어 버튼 누름이 요구하는 작업을 수행한 뒤, 다시 대기로 돌아갑니다.

8.9.3. 세 가지 메서드

  • set() – 플래그를 설정된 것으로 표시합니다. 태스크가 현재 wait() 에서 대기 중이라면 재개되도록 예약합니다. 인터럽트 핸들러에서 호출해도 안전합니다.

  • wait() – 코루틴입니다. 플래그가 설정될 때까지 차단한 뒤 반환합니다. 반환 시 플래그는 자동으로 지워집니다 . wait 를 다시 호출하면 다음 set() 까지 다시 차단됩니다.

  • clear() – 대기하지 않고 플래그를 명시적으로 지웁니다. 첫 wait 이전에 태스크가 준비되기 전에 발생한 가짜 조기 set() 을 버리는 데 유용합니다.

8.9.4. 단일 대기자만 허용

한 번에 하나의 태스크만 wait() 에 있을 수 있습니다. 애플리케이션이 원하는 형태는 플래그를 위한 단일 소유자 태스크(보통 인터럽트 핸들러를 설치한 바로 그 태스크)이며, 다른 태스크들은 Event, Lock, 또는 그러한 프리미티브로 보호되는 공유 데이터 구조를 통해 그 소유자와 통신합니다.

버튼이 여러 태스크를 깨워야 할 때, 소유자 태스크가 분산 처리를 수행할 곳입니다. 위의 watcher는 각 누름 이후 some_event.set() 을 호출할 수 있고, some_event 를 기다리던 모든 태스크가 재개됩니다.