2.32. Coroutiner

En coroutine är en funktion som kan pausa halvvägs och senare återuppta från där den slutade, med alla sina lokala variabler intakta. Den speglar generatormönstret – en funktionskropp som kan avbrytas och återupptas – med en skillnad i vem som driver varje återupptagning.

Pythons syntax för att skriva en coroutine är nyckelordsparet async / await. async markerar funktionen som en coroutine; await markerar punkterna inuti den där paus är tillåten.

2.32.1. Definiera en coroutine

En funktion som definieras med async def är en coroutine-funktion. Att anropa den kör inte kroppen; den returnerar ett coroutine-objekt som ännu inte har startat:

async def greet(name):
    print("hello,", name)

coro = greet("Alice")        # body NOT run yet

Coroutine-objektet är pausat allra först i funktionen. Något måste driva det för att kroppen ska köras – det något är en händelseloop, en körtidskomponent. MicroPython levererar en i modulen asyncio. Betrakta tills vidare coroutinen som ”redo att köra, väntar på en drivare”.

2.32.2. Pausa inuti en coroutine

Inuti en coroutine avbryter ett await-uttryck exekveringen tills det inväntade värdet är klart:

async def fetch_and_log():
    data = await read_sensor()
    print("got:", data)

När kroppen når await read_sensor() lämnar coroutinen tillbaka kontrollen till det som kör den. När read_sensor() är klar återupptar drivaren coroutinen på nästa rad, med resultatet bundet till data.

await är endast giltigt inuti en coroutine. Att använda det i en vanlig funktion är ett syntaxfel.

2.32.3. Förhållande till generatorer

Coroutiner och generatorer delar samma underliggande mekanism. Skillnaden är vem som drar varje återupptagning:

  • En generator lämnar (yield) värden; konsumenten drar nästa med next() eller genom att iterera.

  • En coroutine lämnar (yield) kontroll; en händelseloop schemalägger återupptagningen när den inväntade operationen är klar.

Om handskakningen med generator-yield är begriplig, så är coroutine-handskakningen samma idé – bara driven av en händelseloop i stället för en for-loop.

En händelseloop är en liten avsändare som håller en lista över coroutiner som väntar på något (en timer, en nätverkshändelse, att en annan coroutine blir klar). I varje iteration plockar den en coroutine vars väntan har uppfyllts, återupptar den fram till nästa await, registrerar sedan vad den coroutinen nu väntar på och går vidare till en annan som är redo. Resultatet är att många uppgifter gör framsteg samtidigt på en enda tråd – varje coroutine lämnar frivilligt över kontrollen vid sina await-punkter, och loopen fyller de stunderna med vilka andra coroutiner som helst som är redo att göra framsteg.

Under huven använder await och yield samma körtidsfunktion i Python för att avbryta och återuppta en funktion. Nyckelorden skiljer sig åt eftersom konventionen kring dem gör det: yield lämnar tillbaka ett värde till en konsument som drar med next(); await lämnar kontrollen till en händelseloop som schemalägger återupptagningen när den inväntade operationen är klar. async / await är i grunden nyare syntax för coroutine-mönstret – äldre bibliotek byggde coroutiner direkt ovanpå generatormaskineriet, med hjälp av yield from (introducerat i Iteratorer och generatorer) för att delegera avbrott mellan coroutiner.

2.32.4. Coroutiner behöver en drivare

En coroutine är inert utan en körtid som driver den. Att definiera en är inga problem; att köra en kräver en händelseloop. MicroPythons modul asyncio tillhandahåller den händelseloopen. Avsnittet Asyncio täcker hur du startar loopen, schemalägger coroutiner på den, delar tillstånd mellan dem med lås och händelser, hanterar avbokning och tidsgränser, och formar en riktig applikation kring nyckelorden async / await som introduceras här.