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, відновились би.