8.2. Korutinok és feladatok

A korutinok az a munkaegység, amelyből egy asyncio program felépül; a feladatok pedig azt jelentik, ahogyan egy alkalmazás több korutint futtat párhuzamosan.

8.2.1. Korutinok

A korutin egy async def-fel deklarált függvény:

import asyncio

async def heartbeat(interval_ms):
    while True:
        print("tick")
        await asyncio.sleep_ms(interval_ms)

A törzs úgy néz ki, mint egy szokványos függvényé, egyetlen extra összetevővel: az await-tel. Ahol a korutinnak várnia kell valamire — egy alvásra, egy hálózati olvasásra, egy esemény beállítására —, ott await-el egy olyan kifejezést, amely tudja, hogyan függessze fel a korutint, amíg az, amire vár, készen nem áll. Minden await-nél a korutin visszaadja a vezérlést az asyncio-nak; az asyncio ugyanarról a pontról folytatja, amint a várt művelet befejeződött.

Az asyncio modul kétféle alvást kínál:

  • asyncio.sleep() — az argumentum másodpercben, float-ot fogad el.

  • asyncio.sleep_ms() — az argumentum ezredmásodpercben, int-et vár. Ez egy MicroPython kiterjesztés; általában ez a helyes választás a kamerán, mivel a firmware időzítési beállításai ezredmásodperc-alapúak.

Egy puszta async def önmagában semmit nem csinál. A heartbeat(500) hívása nem hajtja végre a törzset; egy korutin objektumot ad vissza, amelyet az asyncio-nak kell ütemeznie. Egy ilyet a legegyszerűbben az asyncio.run() segítségével ütemezhetsz:

asyncio.run(heartbeat(500))

Az asyncio.run() elindítja az eseményhurkot, ütemezi a neki átadott korutint felső szintű belépési pontként, mozgatja a hurkot, amíg az a korutin vissza nem tér, majd lebontja a hurkot. Egyetlen korutin esetén ez az egész program. Több korutin esetén az alkalmazás a feladatokhoz nyúl.

8.2.2. Feladatok

A feladat az asyncio burkolója egy korutin köré, amely azt mondja: ütemezd ezt az aktuálissal párhuzamosan, és hadd menjek tovább. Az asyncio.create_task() létrehoz egyet, és visszaad egy Task objektumot, amely az ütemezett munkát képviseli:

task = asyncio.create_task(heartbeat("fast", 100))

A korutin most már a hurok ütemezésén van; a hívó nem várt rá. A visszaadott Task az a fogantyú, amelyet a hívó utána a futó munkával való interakcióhoz használ.

Miután az alkalmazásnak megvan a fogantyúja, három dolgot tehet vele:

  • Megvárhatja a feladat befejeződését. Egy Task maga is await-elhető. A result = await task felfüggeszti az aktuális korutint, amíg a task korutinja vissza nem tér, majd azzal folytatja, amit az a korutin visszaadott (vagy újra dobja, amit az dobott).

  • Megszakíthatja a feladatot. A task.cancel() ütemezi, hogy az asyncio.CancelledError a feladat korutinján belül dobódjon a következő await-jénél, lehetőséget adva neki, hogy egy finally blokkban lefuttassa a takarító kódot. A részleteket a időtúllépések és megszakítás oldal tárgyalja.

  • Később azonosíthatja. Az asyncio.current_task() visszaadja az éppen futó korutin Task objektumát. A legtöbb szkript soha nem hívja meg; eszközökben (instrumentációban) és kivételkezelőkben jelenik meg.

A szkriptnek nem kell minden alkalommal elkapnia a fogantyút. Az eldobható háttérfeladatok, amelyeket az alkalmazás elindít és futni hagy, elhagyhatják a visszatérési értéket — a hurok így is ütemezi őket:

import asyncio

async def heartbeat(name, interval_ms):
    while True:
        print(name)
        await asyncio.sleep_ms(interval_ms)

async def main():
    asyncio.create_task(heartbeat("fast", 100))
    asyncio.create_task(heartbeat("slow", 500))
    await asyncio.sleep(5)

asyncio.run(main())

A két create_task hívás mindkét heartbeatet ütemezi anélkül, hogy bármelyikre is várna. A vezérlés azonnal visszatér a main-be, amely ezután await-el egy ötmásodperces alvást. Amíg alszik, a két heartbeat feladat előrehalad; a hurok körbejár azon, amelyik feladat éppen készen áll a futásra. Öt másodperc után a main visszatér, a hurok lebontja a még élő feladatokat, és az asyncio.run() visszatér a hívóhoz.

Akkor kapd el a fogantyút, amikor az alkalmazásnak ténylegesen szüksége van a fenti három művelet valamelyikére. A gyakorlatban ez szinte mindig azt jelenti, mert egy alkalmazás tiszta leállítása az általa indított háttérfeladatok megszakítását jelenti — a megszakítási oldal tárgyalja a mintát.

8.2.3. A kétsoros szabály

A minimális asyncio program az a két sor, amellyel a fenti példák végződnek:

async def main():
    ...

asyncio.run(main())

Minden más — a feladatok, amelyeket az alkalmazás létrehoz, a primitívek, amelyekkel összehangolja őket, a streamek, amelyeket megnyit — a main-en belül történik (és a main által indított korutinokon belül). Amikor egy szkript kinövi a kamera klasszikus while True: csi0.snapshot() ciklusát, a válasz nem az, hogy több helyen hívjuk meg az asyncio.run() függvényt; hanem hogy az új munkát további feladatokként hajtsuk be a main-be.