8.2. Coroutiner och uppgifter¶
Coroutiner är den arbetsenhet som ett asyncio-program byggs av; uppgifter är hur en applikation kör flera coroutiner samtidigt.
8.2.1. Coroutiner¶
En coroutine är en funktion deklarerad med async def
import asyncio
async def heartbeat(interval_ms):
while True:
print("tick")
await asyncio.sleep_ms(interval_ms)
Kroppen ser ut som en vanlig funktion, med en extra ingrediens: await. Varhelst coroutinen måste vänta på något – en sömn, en nätverksläsning, en händelse som sätts – awaitar den ett uttryck som vet hur man pausar coroutinen tills det den väntar på är klart. Vid varje await lämnar coroutinen tillbaka kontrollen till asyncio; asyncio återupptar den från samma punkt när den await:ade operationen har slutförts.
Asyncio-modulen levererar två sömner:
asyncio.sleep()– argument i sekunder, accepterar ett flyttal.asyncio.sleep_ms()– argument i millisekunder, tar ett heltal. En MicroPython-utökning; vanligtvis det rätta valet på kameran eftersom tidsinställningar i fast programvara är millisekundsformade.
En naken async def gör ingenting på egen hand. Att anropa heartbeat(500) exekverar inte kroppen; det returnerar ett coroutine-objekt som asyncio måste schemalägga. Det enklaste sättet att schemalägga ett är asyncio.run()
asyncio.run(heartbeat(500))
asyncio.run() startar händelseloopen, schemalägger coroutinen den fick som ingångspunkt på toppnivå, driver loopen tills den coroutinen returnerar och river sedan ner loopen. För en enda coroutine är det hela programmet. För flera coroutiner tar applikationen till uppgifter.
8.2.2. Uppgifter¶
En uppgift (task) är asyncios omslag runt en coroutine som säger schemalägg denna samtidigt med den nuvarande och låt mig fortsätta. asyncio.create_task() skapar en och returnerar ett Task-objekt som representerar det schemalagda arbetet:
task = asyncio.create_task(heartbeat("fast", 100))
Coroutinen finns nu på loopens schema; anroparen har inte väntat på den. Det returnerade Task-objektet är handtaget som anroparen använder efteråt för att interagera med det körande arbetet.
När applikationen har handtaget kan den göra tre saker med det:
Vänta på att uppgiften slutförs. Ett
Taskär i sig await:bart.result = await taskpausar den nuvarande coroutinen tillstask:s coroutine returnerar och återupptas sedan med vad den coroutinen returnerade (eller återkastar vad den kastade).Avbryt uppgiften.
task.cancel()schemalägger attasyncio.CancelledErrorkastas inuti uppgiftens coroutine vid dess nästaawait, vilket ger den en chans att köra uppstädningskod i ettfinally-block. Sidan om tidsgränser och avbrytning täcker detaljerna.Identifiera den senare.
asyncio.current_task()returnerarTaskför den coroutine som för tillfället körs. De flesta skript anropar den aldrig; den dyker upp i instrumentering och i undantagshanterare.
Skriptet behöver inte fånga handtaget varje gång. Engångs-bakgrundsuppgifter som applikationen startar och låter köra kan släppa returvärdet – loopen schemalägger dem ändå:
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())
De två create_task-anropen schemalägger båda hjärtslagen utan att vänta på något av dem. Kontrollen återgår omedelbart till main, som sedan awaitar en fem sekunder lång sömn. Medan den sover kommer de två hjärtslagsuppgifterna framåt; loopen växlar genom vilken uppgift som än är redo att köra. Efter fem sekunder returnerar main, loopen river ner alla uppgifter som fortfarande är vid liv, och asyncio.run() returnerar till anroparen.
Fånga handtaget när applikationen faktiskt behöver en av de tre operationerna ovan. I praktiken betyder det nästan alltid, eftersom att stänga ner en applikation på ett rent sätt innebär att avbryta de bakgrundsuppgifter den startade – avbrytningssidan täcker mönstret.
8.2.3. Tvåradsregeln¶
Det minimala asyncio-programmet är de två rader som exemplen ovan slutar med:
async def main():
...
asyncio.run(main())
Allt annat – uppgifterna som applikationen skapar, primitiverna som den samordnar dem med, strömmarna som den öppnar – händer inuti main (och inuti de coroutiner som main startar). När ett skript växer ur kamerans klassiska while True: csi0.snapshot()-loop är svaret inte att anropa asyncio.run() på flera ställen; det är att vika in det nya arbetet i main som fler uppgifter.