8.9. ThreadSafeFlag#

asyncio.ThreadSafeFlag – це сигнальний примітив asyncio для випадку, якого Event не підтримує: обробнику переривань потрібно пробудити завдання 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 виконує awaits flag.wait() у циклі; щоразу, коли прапор встановлюється, завдання відновлюється, виконує необхідну обробку натискання кнопки, а потім повертається до очікування.

8.9.3. Три методи#

  • set() – позначити прапор як встановлений. Якщо завдання зараз очікує в wait(), запланувати його відновлення. Безпечно викликати з обробника переривань.

  • wait() – корутина. Блокується до встановлення прапора, потім повертається. Прапор автоматично скидається після повернення – наступний виклик wait буде заблокований знову до наступного set().

  • clear() – явно скинути прапор без очікування на нього. Корисно перед першим wait, щоб відкинути будь-які хибні ранні set(), що спрацювали до готовності завдання.

8.9.4. Лише один очікувач#

Лише одне завдання може перебувати в wait() одночасно. Правильний шаблон – мати одне завдання-власника для прапора, зазвичай те саме завдання, що встановило обробник переривань, а інші завдання спілкуються з ним через Event, Lock або спільні структури даних, захищені цими примітивами.

Коли кнопка має пробуджувати кілька завдань, завдання-власник є місцем розгалуження: наведений вище спостерігач міг би викликати some_event.set() після кожного натискання, і всі завдання, що чекають на some_event, відновились би.