Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Asynchroniczność - równoległe obserwacje

Witaj w Module 6, @name! Darwin tutaj z ekscytującym tematem - asynchroniczne programowanie w Pythonie! ⚡🐍

Do tej pory wszystkie Twoje programy były synchroniczne - wykonywały operacje jedna po drugiej. Teraz poznasz asynchroniczność - technikę pozwalającą na wykonywanie wielu zadań jednocześnie! 🚀

Analogia Safari: Synchroniczne programowanie to jak jeden przewodnik obserwujący zwierzęta po kolei - najpierw lwy (czeka 30 minut), potem słonie (czeka 20 minut), potem gepardy (czeka 15 minut). Asynchroniczne to jak wielu przewodników obserwujących jednocześnie - jeden przy lwach, drugi przy słoniach, trzeci przy gepardach - wszyscy równolegle! ⏱️🦁🐘🐆

Problem z synchronicznym kodem

Synchroniczny kod - czekanie po kolei

1import time
2
3def observe_lions():
4    """Obserwacja lwów - trwa 3 sekundy"""
5    print("🦁 Rozpoczynam obserwację lwów...")
6    time.sleep(3)  # Symulacja czekania
7    print("🦁 Obserwacja lwów zakończona!")
8    return {"species": "Lew", "count": 5}
9
10def observe_elephants():
11    """Obserwacja słoni - trwa 2 sekundy"""
12    print("🐘 Rozpoczynam obserwację słoni...")
13    time.sleep(2)
14    print("🐘 Obserwacja słoni zakończona!")
15    return {"species": "Słoń", "count": 3}
16
17def observe_cheetahs():
18    """Obserwacja gepardów - trwa 4 sekundy"""
19    print("🐆 Rozpoczynam obserwację gepardów...")
20    time.sleep(4)
21    print("🐆 Obserwacja gepardów zakończona!")
22    return {"species": "Gepard", "count": 2}
23
24# Synchroniczne wykonanie - PO KOLEI
25start = time.time()
26result1 = observe_lions()      # Czeka 3s
27result2 = observe_elephants()  # Potem czeka 2s
28result3 = observe_cheetahs()   # Potem czeka 4s
29end = time.time()
30
31print(f"\nCałkowity czas: {end - start:.1f}s")
32# Output: Całkowity czas: 9.0s (3 + 2 + 4)

Problem: Czas = 9 sekund! Musimy czekać na każdą obserwację po kolei, mimo że moglibyśmy obserwować równolegle! ⏰❌

Rozwiązanie - asynchroniczny kod

Asynchroniczny kod - równoległe czekanie

1import asyncio
2
3async def observe_lions():
4    """Asynchroniczna obserwacja lwów"""
5    print("🦁 Rozpoczynam obserwację lwów...")
6    await asyncio.sleep(3)  # await zamiast time.sleep!
7    print("🦁 Obserwacja lwów zakończona!")
8    return {"species": "Lew", "count": 5}
9
10async def observe_elephants():
11    """Asynchroniczna obserwacja słoni"""
12    print("🐘 Rozpoczynam obserwację słoni...")
13    await asyncio.sleep(2)
14    print("🐘 Obserwacja słoni zakończona!")
15    return {"species": "Słoń", "count": 3}
16
17async def observe_cheetahs():
18    """Asynchroniczna obserwacja gepardów"""
19    print("🐆 Rozpoczynam obserwację gepardów...")
20    await asyncio.sleep(4)
21    print("🐆 Obserwacja gepardów zakończona!")
22    return {"species": "Gepard", "count": 2}
23
24async def main():
25    """Główna funkcja async"""
26    start = time.time()
27
28    # Wykonaj równolegle!
29    results = await asyncio.gather(
30        observe_lions(),
31        observe_elephants(),
32        observe_cheetahs()
33    )
34
35    end = time.time()
36    print(f"\nCałkowity czas: {end - start:.1f}s")
37    # Output: Całkowity czas: 4.0s (max z 3, 2, 4)
38    print(f"Wyniki: {results}")
39
40# Uruchom async main
41asyncio.run(main())

Wynik: Czas = 4 sekundy! Wszystkie obserwacje wykonują się równolegle! ⚡✅

Oszczędność: 9s → 4s = 5 sekund szybciej (55% redukcja)!

Async/await - składnia

async def - definicja funkcji asynchronicznej

1# Synchroniczna funkcja
2def sync_function():
3    return "Sync result"
4
5# Asynchroniczna funkcja (coroutine)
6async def async_function():
7    return "Async result"

async def
tworzy coroutine - specjalną funkcję, która może być "zawieszona" (
await
) i wznowiona później.

await - czekanie na wynik

1async def fetch_species_data(species_id):
2    print(f"Pobieranie danych dla gatunku {species_id}...")
3    await asyncio.sleep(2)  # Symulacja I/O operation
4    return {"id": species_id, "name": "Lew", "population": 120}
5
6async def main():
7    # await zawiesza wykonanie dopóki nie otrzyma wyniku
8    data = await fetch_species_data(1)
9    print(f"Otrzymano dane: {data}")
10
11asyncio.run(main())

await
mówi: "Czekaj na wynik, ale w międzyczasie pozwól innym zadaniom działać".

⚠️ WAŻNE:

await
można używać tylko wewnątrz
async def
!

Event Loop - silnik async

Event loop to serce asynchroniczności - zarządza wszystkimi coroutines i przełącza między nimi.

Analogia: Event loop to jak dispatcher safari - przydziela przewodników do różnych zadań, przełącza między nimi, i zbiera wyniki! 🎯

1import asyncio
2
3async def task1():
4    print("Task 1 start")
5    await asyncio.sleep(1)
6    print("Task 1 end")
7
8async def task2():
9    print("Task 2 start")
10    await asyncio.sleep(0.5)
11    print("Task 2 end")
12
13async def main():
14    # Event loop zarządza tymi taskami
15    await asyncio.gather(task1(), task2())
16
17# asyncio.run() tworzy event loop i wykonuje main()
18asyncio.run(main())

Output:

1Task 1 start
2Task 2 start
3Task 2 end  (po 0.5s)
4Task 1 end  (po 1s)

Event loop przełączał się między task1 i task2!

asyncio.gather() - równoległe wykonanie

asyncio.gather()
wykonuje wiele coroutines równolegle i zwraca listę wyników:

1import asyncio
2
3async def get_species(species_id):
4    await asyncio.sleep(1)
5    return {"id": species_id, "name": f"Species {species_id}"}
6
7async def main():
8    # Pobierz 5 gatunków równolegle
9    results = await asyncio.gather(
10        get_species(1),
11        get_species(2),
12        get_species(3),
13        get_species(4),
14        get_species(5)
15    )
16
17    print(f"Pobrano {len(results)} gatunków:")
18    for species in results:
19        print(f"  - {species['name']}")
20
21asyncio.run(main())

Czas: 1 sekunda (zamiast 5 sekund synchronicznie)! ⚡

gather()
zwraca wyniki w tej samej kolejności, w jakiej podałeś coroutines!

asyncio.create_task() - uruchom w tle

create_task()
uruchamia coroutine w tle (nie czeka na wynik):

1async def background_observation(species):
2    print(f"🔍 Rozpoczynam obserwację {species}...")
3    await asyncio.sleep(3)
4    print(f"✅ Obserwacja {species} zakończona!")
5
6async def main():
7    # Uruchom w tle
8    task1 = asyncio.create_task(background_observation("Lew"))
9    task2 = asyncio.create_task(background_observation("Słoń"))
10
11    print("Robię inne rzeczy w międzyczasie...")
12    await asyncio.sleep(1)
13    print("Nadal robię inne rzeczy...")
14
15    # Czekaj na zakończenie tasków
16    await task1
17    await task2
18
19asyncio.run(main())

Output:

1🔍 Rozpoczynam obserwację Lew...
2🔍 Rozpoczynam obserwację Słoń...
3Robię inne rzeczy w międzyczasie...
4Nadal robię inne rzeczy...
5✅ Obserwacja Lew zakończona!
6✅ Obserwacja Słoń zakończona!

Async comprehensions

Python pozwala na async list/dict comprehensions:

1async def get_population(species_id):
2    await asyncio.sleep(0.1)
3    return species_id * 10
4
5async def main():
6    # Async list comprehension
7    populations = [await get_population(i) for i in range(1, 6)]
8    print(f"Populacje: {populations}")
9    # Output: Populacje: [10, 20, 30, 40, 50]
10
11    # Ale to wykonuje się SEKWENCYJNIE!
12    # Lepiej użyć gather():
13    populations = await asyncio.gather(
14        *[get_population(i) for i in range(1, 6)]
15    )
16    print(f"Populacje (równolegle): {populations}")
17
18asyncio.run(main())

Praktyczny przykład - Safari API Client

Asynchroniczny klient do API Safari:

1import asyncio
2import aiohttp  # pip install aiohttp
3
4class SafariAPIClient:
5    def __init__(self, base_url: str):
6        self.base_url = base_url
7
8    async def get_species(self, species_id: int):
9        """Pobierz dane gatunku"""
10        async with aiohttp.ClientSession() as session:
11            async with session.get(f"{self.base_url}/species/{species_id}") as response:
12                return await response.json()
13
14    async def get_multiple_species(self, species_ids: list[int]):
15        """Pobierz wiele gatunków równolegle"""
16        async with aiohttp.ClientSession() as session:
17            tasks = []
18            for species_id in species_ids:
19                task = session.get(f"{self.base_url}/species/{species_id}")
20                tasks.append(task)
21
22            responses = await asyncio.gather(*tasks)
23            results = []
24            for response in responses:
25                data = await response.json()
26                results.append(data)
27
28            return results
29
30async def main():
31    client = SafariAPIClient("https://api.safari-db.com")
32
33    # Pobierz 10 gatunków równolegle
34    species_ids = list(range(1, 11))
35    start = time.time()
36    results = await client.get_multiple_species(species_ids)
37    end = time.time()
38
39    print(f"Pobrano {len(results)} gatunków w {end - start:.2f}s")
40
41asyncio.run(main())

Synchronicznie: 10 requests × 0.5s = 5 sekund Asynchronicznie: max(0.5s) = 0.5 sekundy

Kiedy używać async?

✅ Używaj async gdy:

  • I/O operations - zapytania HTTP, bazy danych, pliki
  • Wiele równoległych operacji - 100+ requests do API
  • WebSockets - real-time komunikacja
  • Oczekiwanie - sleep, timery, długie obliczenia

❌ NIE używaj async gdy:

  • CPU-bound - obliczenia matematyczne, przetwarzanie obrazów
  • Prosty kod - mała aplikacja bez I/O
  • Brak bibliotek async - biblioteka nie wspiera async

Dla CPU-bound użyj multiprocessing zamiast async!

Async context managers

1class AsyncDatabaseConnection:
2    async def __aenter__(self):
3        print("Łączenie z bazą...")
4        await asyncio.sleep(1)
5        return self
6
7    async def __aexit__(self, exc_type, exc_val, exc_tb):
8        print("Zamykanie połączenia...")
9        await asyncio.sleep(0.5)
10
11    async def query(self, sql):
12        print(f"Wykonuję: {sql}")
13        await asyncio.sleep(0.2)
14        return [{"id": 1, "name": "Lew"}]
15
16async def main():
17    async with AsyncDatabaseConnection() as db:
18        results = await db.query("SELECT * FROM species")
19        print(f"Wyniki: {results}")
20
21asyncio.run(main())

Obsługa błędów w async

1async def risky_operation(species_id):
2    if species_id == 3:
3        raise ValueError(f"Gatunek {species_id} nie istnieje!")
4    await asyncio.sleep(1)
5    return {"id": species_id}
6
7async def main():
8    try:
9        results = await asyncio.gather(
10            risky_operation(1),
11            risky_operation(2),
12            risky_operation(3),  # Błąd!
13            return_exceptions=True  # Zwróć wyjątki zamiast rzucać
14        )
15
16        for i, result in enumerate(results, 1):
17            if isinstance(result, Exception):
18                print(f"Gatunek {i}: BŁĄD - {result}")
19            else:
20                print(f"Gatunek {i}: OK - {result}")
21
22    except Exception as e:
23        print(f"Błąd: {e}")
24
25asyncio.run(main())

Output:

1Gatunek 1: OK - {'id': 1}
2Gatunek 2: OK - {'id': 2}
3Gatunek 3: BŁĄD - Gatunek 3 nie istnieje!

Podsumowanie

W tej lekcji nauczyłeś/aś się:

  • ✅ Sync vs Async - różnice i korzyści
  • async def
    i
    await
    - składnia
  • ✅ Event loop - silnik asynchroniczności
  • asyncio.gather()
    - równoległe wykonanie
  • asyncio.create_task()
    - taski w tle
  • ✅ Async comprehensions
  • ✅ Praktyczne przykłady Safari API
  • ✅ Kiedy używać async (I/O) vs multiprocessing (CPU)
  • ✅ Async context managers i error handling

Analogia Safari finalna: Async to jak wielu przewodników safari obserwujących różne gatunki jednocześnie - zamiast czekać 9 sekund obserwując po kolei (sync), obserwują równolegle i kończą w 4 sekundy (async)! Event loop to dispatcher koordynujący wszystkich przewodników! ⏱️🦁🐘🐆

Następna lekcja: Darwin pokaże Ci FastAPI - nowoczesny framework async do budowania błyskawicznych API! 🚀🌐

Vai a CodeWorlds