Wyjaśnienie asynchronizacji / oczekiwania w Pythonie: samouczek 2026
Async w Pythonie ma reputację mylącego. Nie musi tak być. Kiedy już zinternalizujesz jedno zdanie — async pozwala JEDNEM wątkowi wykonywać wiele zadań związanych z we/wy przeplatanych — wszystko inne następuje.
W tym samouczku omówiono: co właściwie robią „async def” i „await”, kiedy async wygrywa, a kiedy jest to niewłaściwe narzędzie, cztery wzorce używane przez każdą bazę kodu asynchronicznego (sekwencja, zbieranie, semafor, limit czasu) oraz trzy sposoby, w jakie początkujący po cichu przełykają błędy.
Jednozdaniowy model mentalny
Async w Pythonie to współbieżność w jednym wątku. Kiedy Twój kod otrzyma komunikat „oczekuj”, sterowanie powraca do pętli zdarzeń, która może uruchomić inne zadanie, podczas gdy pierwsze czeka na wejście/wyjście. Po zakończeniu operacji we/wy pętla wznawia pierwsze zadanie od miejsca, w którym została wstrzymana.
To wszystko. Żadnych wątków, żadnych procesów, żadnej równoległości (w sensie CPython-with-GIL). Tylko jeden wątek żonglujący wieloma zadaniami, które większość czasu spędzają w oczekiwaniu na sieć/dysk/bazę danych.
Asynchronizacja jest szybsza, gdy wąskie gardło czeka, a nie wykonuje obliczenia.
Minimalny wykonalny program asynchroniczny składający się z trzech linijek
async def hello()— deklaruje współprogram. Wywołaniehello()nie powoduje uruchomienia kodu; tworzy obiekt współprogramowy.await asyncio.sleep(1)— przywraca kontrolę do pętli zdarzeń na 1 sekundę.asyncio.run(hello())— tworzy pętlę zdarzeń i uruchamia współprogram do końca.
Reguła: `await` tylko wewnątrz `async def`. Wywołanie „await” w zwykłej funkcji jest „SyntaxError”.
Kiedy wygrywa asynchronizacja (i kiedy nie)
Async wygrywa: praca związana z we/wy
Każde zadanie, które większość czasu zegara ściennego spędza na CZEKANIU, jest kandydatem do wykonania asynchronicznego:
- Żądania HTTP (zewnętrzne API, scraping, webhooki)
- Zapytania do bazy danych
- Plik odczytuje/zapisuje
- Strumienie WebSocket / gRPC
- Konsumenci w kolejce (Redis, RabbitMQ, Kafka)
Przykład: pobieranie 100 adresów URL.
Przyspieszenie zegara ściennego 50-100x do pracy wymagającej dużej liczby wejść/wyjść. To tutaj asynchronia zarabia na swój budżet złożoności.
Async LOSES: praca związana z procesorem
Async nie zapewnia równoległości obliczeń. Ponieważ jest to jednowątkowa, kooperacyjna pętla, gorąca pętla procesora blokuje całą pętlę zdarzeń.
Wszystkie inne zadania zawieszają się podczas działania pbkdf2. W przypadku pracy związanej z procesorem sięgnij po „multiprocessing” lub „concurrent.futures.ProcessPoolExecutor”, a nie asynchronicznie.
Mieszane obciążenie: run_in_executor
Jeśli JEDEN krok obciąża procesor, a reszta to operacje we/wy, zawiń krok procesora tak, aby działał w puli wątków bez blokowania pętli:
Brak = domyślna pula wątków. Świetnie nadaje się do wywoływania bibliotek synchronizacji innych firm, które są powolne, ale nie można ich przepisać.
Cztery wzory
1. Sekwencyjny — jedno zadanie na raz
Użyj, gdy każde żądanie zależy od wyniku poprzedniego.
2. asyncio.gather — odpal wszystko na raz
Domyślny wybór dla niezależnych zadań we/wy. Ale: jeśli zbierzesz 10 000 zadań w ramach jednego interfejsu API, otrzymasz deskryptory plików o ograniczonej szybkości lub wyczerpaniu. Tutaj właśnie pojawia się wzór semafora.
3. Semafor — ograniczenie współbieżności
asyncio.Semaphore(N) pozwala w dowolnym momencie maksymalnie N zadań przekroczyć async z sem:. Kolejka pozostałych zadań. Użyj dla dowolnego zewnętrznego API z limitem szybkości lub bazy danych z pulą połączeń.
4. Limit czasu — porzuć powolną pracę
asyncio.wait_for anuluje podstawowe zadanie po N sekundach. Niezbędny dla każdego kodu asynchronicznego komunikującego się z siecią — nigdy nie wykonuj nieograniczonego oczekiwania.
Trzy pułapki połykające błędy
Pułapka 1: asyncio.gather z return_exceptions=True
Przydatne, gdy chcesz zachować częściowe wyniki, ale ŚMIERTELNE, jeśli nie sprawdzasz wszystkich wyników:
Bez sprawdzenia błędy po cichu znikają.
Pułapka 2: Zadania odpal i zapomnij
Jeśli funkcja background_work() zostanie podniesiona, Python wyemituje ostrzeżenie przy zamykaniu i przejdzie dalej. W przypadku czegokolwiek, na czym Ci zależy, przechowuj zadanie i „czekaj” na nie lub użyj asyncio.TaskGroup (3.11+), które propaguje wszystkie błędy.
Pułapka 3: Synchronizuj kod w trybie asynchronicznym
„żądania” są synchroniczne. Nawet wewnątrz async def requests.get blokuje całą pętlę do czasu nadejścia odpowiedzi. Użyj httpx (lub aiohttp) dla asynchronicznego HTTP.
Zasada: nigdy nie używaj synchronicznej biblioteki we/wy w kodzie asynchronicznym. Jeśli biblioteka nie ma wersji asynchronicznej, zawiń ją w run_in_executor.
asyncio.TaskGroup — sposób na rozproszenie w 2026 roku
W Pythonie 3.11 dodano grupę zadań „TaskGroup”, która rozwiązuje problem „wystrzel i zapomnij”:
Ustrukturyzowana współbieżność — brak utraconych błędów i osieroconych zadań. Wolę to niż nagie „tworzenie_zadania” we współczesnym kodzie.
Przykład z życia wziętego: skrobak z ograniczoną szybkością
Wszystkie cztery wzorce w 15 liniach: ograniczona współbieżność (semafor), przekroczenia limitu czasu (wait_for), jawne gałęzie błędów (bez połykania) i zbieranie w celu rozwinięcia. Skopiuj i wklej ten kształt do dowolnego asynchronicznego zadania we/wy.
Async w Pythonie wymaga prawdziwego uczenia się, ale model mentalny jest prosty: jeden wątek, kooperacyjna pauza na „oczekiwanie”, operacje we/wy nakładają się. Opanuj zebierz + Semafor + wait_for + TaskGroup, a wykonasz 95% pracy asynchronicznej, jaką kiedykolwiek napiszesz.
Następny krok: Ścieżka dla seniorów w języku Python zawiera 20 praktycznych lekcji na temat asyncio, TaskGroup, aiohttp/httpx i powyższych wzorców współbieżności — z prawdziwym Pythonem uruchomionym w przeglądarce.