CodeGym /Kursy /Python SELF PL /Metody asynchroniczne

Metody asynchroniczne

Python SELF PL
Poziom 25 , Lekcja 2
Dostępny

3.1 Moduł asyncio

Swoich wątków dla asynchronicznych podzadań już dawno nikt nie tworzy. W sumie, można je tworzyć, ale takie działania są uważane za zbyt niskopoziomowe i stosowane jedynie przez twórców frameworków. I to tylko wtedy, gdy się bez nich nie obejdzie.

Obecnie na topie jest programowanie asynchroniczne, operatory async/await i korutyny z zadaniami (tasks). Ale po kolei…

Trochę historii

Początkowo w Pythonie do rozwiązywania zadań asynchronicznego programowania używano korutyn, opartych na generatorach. Potem, w Pythonie 3.4, pojawił się moduł asyncio (czasem jego nazwę zapisują jako async IO), w którym zrealizowano mechanizmy asynchronicznego programowania. W Pythonie 3.5 pojawiła się konstrukcja async/await.

A teraz trochę wprowadzenia. Najpierw opowiem krótko o tych wszystkich rzeczach, potem szczegółowiej, a następnie jeszcze bardziej szczegółowo. Nie da się inaczej, gdyż prawie wszystkie one działają w pakiecie i nie da się szczegółowo wytłumaczyć działania jednej funkcji bez odniesienia się do innych.

Moduł asyncio

Moduł asyncio jest przeznaczony do pisania programów asynchronicznych, zapewniając możliwość równoległego wykonywania zadań. Wspiera operacje asynchroniczne wejścia-wyjścia, timery, sockety, wykonywanie korutyn i wielowątkowość, działając w jednym lub kilku pętlach zdarzeń.

Korutyny (Coroutines)

Korutyny to funkcje asynchroniczne, definiowane za pomocą słowa kluczowego async def. Korutyny pozwalają zawieszać swoje wykonywanie za pomocą słowa kluczowego await, co pozwala innym korutynom wykonywać się w tym czasie.


import asyncio

# Definicja funkcji asynchronicznej (korutyny)
async def main():
    print('Hello ...')
    # Zawieszamy wykonywanie na 1 sekundę
    await asyncio.sleep(1)
    print('... World!')

# Uruchomienie funkcji asynchronicznej main() w pętli zdarzeń
asyncio.run(main())

Pętla zdarzeń (Event Loop)

Pętla zdarzeń kontroluje wykonywanie korutyn, zadań i innych operacji asynchronicznych. Wywołanie asyncio.run() uruchamia pętlę zdarzeń i wykonuje korutynę aż do jej zakończenia.


import asyncio

async def main():
    print('Hello ...')
    await asyncio.sleep(1)
    print('... World!')

# Pobranie bieżącej pętli zdarzeń
loop = asyncio.get_event_loop()
# Uruchomienie korutyny aż do jej zakończenia
loop.run_until_complete(main())
# Zamknięcie pętli zdarzeń po zakończeniu wszystkich zadań
loop.close()

Zadania (Tasks)

Zadania pozwalają uruchamiać korutyny równolegle. Tworzone są za pomocą asyncio.create_task() lub asyncio.ensure_future().


import asyncio

# Definicja korutyny, która będzie wykonana z opóźnieniem
async def say_after(delay, what):
    # Zawieszamy wykonywanie na podany czas
    await asyncio.sleep(delay)
    print(what)

# Główna korutyna
async def main():
    # Tworzymy zadania dla równoległego wykonywania korutyn
    task1 = asyncio.create_task(say_after(1, 'hello'))
    task2 = asyncio.create_task(say_after(2, 'world'))
    
    # Czekamy na zakończenie obu zadań
    await task1
    await task2

# Uruchomienie głównej korutyny
asyncio.run(main())

Futures (Futures)

Obiekty Future reprezentują wyniki operacji asynchronicznych, które będą dostępne w przyszłości. Na przykład, są one używane do oczekiwania na zakończenie asynchronicznego zadania.


import asyncio

# Definicja korutyny, która symuluje długie zadanie
async def long_running_task():
    print('Task started')
    # Zawieszamy wykonywanie na 3 sekundy
    await asyncio.sleep(3)
    print('Task finished')
    return 'Result'

# Główna korutyna
async def main():
    # Tworzymy future do oczekiwania na zakończenie zadania
    future = asyncio.ensure_future(long_running_task())
    # Czekamy na zakończenie zadania i pobieramy wynik
    result = await future  
    print(f'Task result: {result}')

# Uruchomienie głównej korutyny
asyncio.run(main())

3.2 Asynchroniczna funkcja — async def

Asynchroniczna funkcja deklaruje się tak samo jak zwykła, tylko przed słowem kluczowym def trzeba napisać słowo async.


async def NazwaFunkcji(parametry):
    kod funkcji

Asynchroniczna funkcja jest deklarowana jak zwykła, wywoływana jak zwykła, ale wynik zwraca inny. Jeśli wywołać asynchroniczną funkcję, to nie zwróci ona wyniku, lecz specjalny obiekt — korutynę.

Można to nawet sprawdzić:


import asyncio

async def main():
    print("Hello World")
            
# Wywołanie funkcji asynchronicznej, która zwraca korutynę
result = main()
# Sprawdzenie typu wyniku
print(type(result)) # <class 'coroutine'>

Co się dzieje? Kiedy oznaczasz funkcję słowem async, to właściwie dodajesz do niej dekorator, który robi mniej więcej to:


def async_decorator(func):
    # Tworzymy obiekt Task
    task = Task()
    # Przekazujemy do niego naszą funkcję func, żeby ją wykonał
    task.target = func  
    # Dodajemy obiekt task do kolejki zadań — Event Loop
    eventloop.add_task(task)  
    # Zwracamy obiekt task
    return task 

Twój kod staje się podobny do:


import asyncio

@async_decorator
def main():
    print("Hello World")
            
result = main()
print(type(result)) # <class 'coroutine'>

Sens tej analogii jest następujący:

Kiedy wywołujesz funkcję asynchroniczną, tworzony jest specjalny obiekt Task, który będzie wykonywał twoją funkcję, ale kiedykolwiek w przyszłości. Może za 0.0001 sekundy, a może dopiero za 10.

Ten obiekt task jest ci od razu zwracany jako wynik wywołania twojej funkcji asynchronicznej. To jest właśnie korutyna. Twoja funkcja asynchroniczna mogła jeszcze w ogóle nie zaczęła się wykonywać, a obiekt task (korutyna) już masz.

Po co ci ten task (korutyna)? Niewiele możesz z nim zrobić, ale można zrobić 3 rzeczy:

  • Poczekać, aż funkcja asynchroniczna się wykona.
  • Poczekać, aż funkcja asynchroniczna zakończy się i uzyskać z korutiny wynik wykonania funkcji.
  • Poczekać, aż wykona się 10 (dowolna liczba) funkcji asynchronicznych.

Jak to zrobić, opowiem poniżej.

3.3 Operator await

Większość działań z korutyną zaczyna się od „poczekaj na wykonanie funkcji asynchronicznej". Dlatego dla tej czynności mamy specjalny operator await.

Musisz po prostu napisać go przed korutyną:


await korutyna

Lub od razu przed wywołaniem funkcji asynchronicznej:


await funkcja_asynchroniczna(argumenty)

Kiedy Python napotyka w kodzie operator await, to wstrzymuje wykonywanie bieżącej funkcji i czeka, aż korutyna się wykona — aż zakończy się funkcja asynchroniczna, do której odwołuje się korutyna.

Ważne! Operator await stosuje się tylko wewnątrz funkcji asynchronicznej do wstrzymania wykonywania do momentu, aż zakończy się inna korutyna lub operacja asynchroniczna.

Robi się to po to, by uprościć proces przełączania między wywołaniami funkcji asynchronicznych. Takie wywołanie await — to de facto deklaracja „będziemy tutaj czekać nie wiadomo jak długo — zajmijcie się wykonywaniem innych funkcji asynchronicznych".

Przykład:


import asyncio

# Definicja funkcji asynchronicznej
async def async_print(text):
    print(text)
        
# Główna funkcja asynchroniczna
async def main():
    # Używamy await, by poczekać na wykonanie funkcji asynchronicznej
    await async_print("Hello World")
        
# Uruchomienie głównej pętli zdarzeń i wykonanie korutyny main()
asyncio.run(main()) #uruchamia funkcję asynchroniczną

W rzeczywistości operator await działa jeszcze sprytniej — również zwraca wynik wykonania funkcji asynchronicznej, dla której został wywołany.

Przykład:


import asyncio

# Definicja funkcji asynchronicznej, która dodaje dwie liczby
async def async_add(a, b):
    return a + b
        
# Główna funkcja asynchroniczna
async def main():
    # Używamy await, by uzyskać wynik wykonania async_add
    sum = await async_add(100, 200)
    print(sum)
        
# Uruchomienie głównej pętli zdarzeń i wykonanie korutyny main()
asyncio.run(main()) #uruchamia funkcję asynchroniczną

A więc, podsumowując, operator await:

  • Wstrzymuje bieżącą funkcję asynchroniczną do momentu, aż zakończy się inna korutyna lub operacja asynchroniczna.
  • Zwraca wynik wykonania operacji asynchronicznej lub korutyny.
  • Można stosować go tylko wewnątrz funkcji asynchronicznej.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION