2.32. Korutyny

Korutyna to funkcja, która może zatrzymać się w trakcie wykonywania i później wznowić działanie od miejsca, w którym je przerwała, zachowując wszystkie swoje zmienne lokalne. Odzwierciedla wzorzec generatora – ciało funkcji, które można zawiesić i wznowić – z jedną zmianą dotyczącą tego, kto steruje każdym wznowieniem.

Składnia Pythona służąca do pisania korutyn to para słów kluczowych async / await. async oznacza funkcję jako korutynę; await oznacza miejsca wewnątrz niej, w których dozwolone jest wstrzymanie.

2.32.1. Definiowanie korutyny

Funkcja zdefiniowana za pomocą async def jest funkcją korutyny. Jej wywołanie nie uruchamia ciała; zwraca ono obiekt korutyny, który jeszcze się nie rozpoczął:

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

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

Obiekt korutyny jest wstrzymany na samym początku funkcji. Coś musi sterować nim, aby uruchomić jego ciało – tym czymś jest pętla zdarzeń, komponent środowiska uruchomieniowego. MicroPython dostarcza ją w module asyncio. Na razie traktuj korutynę jako „gotową do uruchomienia, czekającą na sterownik”.

2.32.2. Wstrzymywanie wewnątrz korutyny

Wewnątrz korutyny wyrażenie await zawiesza wykonywanie do czasu, gdy oczekiwana wartość będzie gotowa:

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

Gdy ciało osiąga await read_sensor(), korutyna oddaje sterowanie temu, co ją uruchamia. Gdy read_sensor() zakończy działanie, sterownik wznawia korutynę w następnym wierszu, z wynikiem przypisanym do data.

await jest poprawne tylko wewnątrz korutyny. Użycie go w zwykłej funkcji jest błędem składni.

2.32.3. Związek z generatorami

Korutyny i generatory dzielą ten sam podstawowy mechanizm. Różnica polega na tym, kto inicjuje każde wznowienie:

  • Generator zwraca wartości; konsument pobiera kolejną za pomocą next() lub przez iterację.

  • Korutyna oddaje sterowanie; pętla zdarzeń planuje wznowienie, gdy oczekiwana operacja jest gotowa.

Jeśli mechanizm uzgadniania w generatorach (yield) ma sens, mechanizm uzgadniania w korutynach jest tą samą ideą – tyle że napędzaną przez pętlę zdarzeń, a nie przez pętlę for.

Pętla zdarzeń to mały dyspozytor, który utrzymuje listę korutyn oczekujących na coś (licznik czasu, zdarzenie sieciowe, zakończenie innej korutyny). W każdej iteracji wybiera korutynę, której warunek oczekiwania został spełniony, wznawia ją do następnego await, następnie zapisuje, na co ta korutyna teraz oczekuje, i przechodzi do kolejnej gotowej. Rezultatem jest wiele zadań robiących postępy współbieżnie w pojedynczym wątku – każda korutyna dobrowolnie oddaje sterowanie w swoich punktach await, a pętla wypełnia te chwile dowolnymi innymi korutynami, które są gotowe robić postępy.

Pod spodem await i yield korzystają z tej samej funkcji środowiska uruchomieniowego Pythona służącej do zawieszania i wznawiania funkcji. Słowa kluczowe różnią się, ponieważ różni się otaczająca je konwencja: yield oddaje wartość konsumentowi pobierającemu ją za pomocą next(); await oddaje sterowanie pętli zdarzeń, która planuje wznowienie, gdy oczekiwana operacja jest gotowa. async / await to w istocie nowsza składnia dla wzorca korutyn – starsze biblioteki budowały korutyny bezpośrednio na mechanizmie generatorów, używając yield from (wprowadzonego w Iteratory i generatory) do delegowania zawieszania między korutynami.

2.32.4. Korutyny potrzebują sterownika

Korutyna jest bezczynna bez środowiska uruchomieniowego, które nią steruje. Zdefiniowanie jej jest w porządku; jej uruchomienie wymaga pętli zdarzeń. Moduł asyncio w MicroPythonie dostarcza tę pętlę zdarzeń. Sekcja Asyncio omawia, jak uruchomić pętlę, zaplanować na niej korutyny, współdzielić między nimi stan za pomocą blokad i zdarzeń, obsługiwać anulowanie i limity czasu oraz ukształtować rzeczywistą aplikację wokół wprowadzonych tutaj słów kluczowych async / await.