Asynchrónne programovanie v Pythone

1. Prečo vôbec async?

Predstav si, že program:

  • čaká na odpoveď zo servera 🌐
  • číta súbor 📂
  • alebo komunikuje s databázou 🗄️

Procesor stále musí čakať na vykonanie vstupno výstupnej operácie

Klasický (synchronný) prístup:

data = download()
process(data)

➡️ Program stojí a čaká… nič iné nerobí.

2. Základná myšlienka async

Ak nie je k dispozícii vstup alebo výstup, tak sa venujem počítaniu v rámci toho istého procesu.

  • nevytváraš nové vlákna (threads)

Vykonávanie viac úloh naraz v jednom procese:

Jedna úloha trá krátko a procesor prepína medzi úlohami:

task1() task2() task1() task1() task2()

Implementácia môže vyzerať takto, ak máme zoznam úloh:

while True: for each taska in tasks: if task.is_ready(): task.run()

6. Event loop

Event loop je:

„Mozog async systému”

  • riadi úlohy
  • prepína medzi nimi
  • rozhoduje, čo sa vykoná ďalej

Spustenie:

asyncio.run(main())

Toto je syntaktický cukr — pod kapotou vytvorí a spustí event loop.

Manuálne vytvorenie:

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(main())
loop.close()

Používa sa pri:

  • testovaní
  • vložení async kódu do existujúcej aplikácie
  • potrebe event loop bežať dlhšie

Ako funguje event loop?

Zjednodušíme si situáciu - Máme jeden súbor a jednu úlohu:

Zo súboru sa niekedy dá čítať, niekedy nie.

Udalosť znamená, že sa čítať dá,

Metóda is_ready() vráti True ak sa momentálne dá čítať, False ak sa momentálne nedá.

Metóda read() vráti všetky dáta ktoré sa momentálne dajú prečítať, potom skončí.

Metóda robí nejaký výpočet, ktorý trvá obmedzený čas.

V cykle:

buffer = [] while running: if file.is_ready(): data.append(file.read()) task.run(buffer)

Takto dosiahneme, pre program nečaká, ale ak sa dá, tak alebo číta dáta alebo počíta.

Korutina

Čo ak máme viac rôznych úloh?

def corroutine(): memory = [] def process_buffer(data):

Urob nieco s datami

    memory.add(data)
    process(data)
return process_buffer

3. Kľúčové pojmy

🔹 async def

Definuje asynchrónnu funkciu — coroutine.

async def hello():
    print(“Ahoj”)

🔹 Coroutine

Funkcia definovaná pomocou async def sa nazýva coroutine.

Volanie coroutine nevráti výsledok, ale objekt coroutine:

<coroutine object hello at 0x...>

Pre vykonanie potrebuješ await:

await hello()

🔹 await

Používa sa na „čakanie” na výsledok bez blokovania event loopu.

await asyncio.sleep(1)

➡️ počas čakania môže bežať iný kód.

🔹 asyncio

Hlavná knižnica pre async v Pythone.

4. Prvý príklad

import asyncio

async def task1():
    print(“Task 1 začína”)
    await asyncio.sleep(2)
    print(“Task 1 končí”)

async def task2():
    print(“Task 2 začína”)
    await asyncio.sleep(1)
    print(“Task 2 končí”)

async def main():
    await task1()
    await task2()

asyncio.run(main())

👉 Toto je stále sekvenčné (najprv task1, potom task2).

5. Paralelné spustenie (concurrency)

Použijeme asyncio.gather():

async def main():
    await asyncio.gather(task1(), task2())

Výsledok:

  • obe úlohy bežia „naraz”
  • celkový čas ≈ najdlhšia úloha (2 sekundy)

7. Concurrency vs Parallelism

Veľmi dôležitý rozdiel.

Concurrency

Úloha A
Úloha B
Úloha A
Úloha B

Prepínanie medzi úlohami.

Parallelism

CPU 1 → Úloha A
CPU 2 → Úloha B

Skutočne súčasné vykonávanie.

👉 Async = concurrency

👉 Multiprocessing = parallelism

8. Tasky

Task umožňuje spustiť coroutine na pozadí a mať nad ňou kontrolu.

task = asyncio.create_task(task1())
await task

asyncio.create_task() v praxi

import asyncio

async def work():
    await asyncio.sleep(2)
    print(“Hotovo”)

async def main():
    task = asyncio.create_task(work())

    print(“Robím niečo iné...”)
    await task

asyncio.run(main())

Výstup:

Robím niečo iné...
Hotovo

9. Časové limity (Timeout)

Nikdy nechceme čakať večne.

import asyncio

async def slow():
    await asyncio.sleep(10)

async def main():
    try:
        await asyncio.wait_for(slow(), timeout=3)
    except asyncio.TimeoutError:
        print(“Timeout!”)

asyncio.run(main())

10. Zrušenie úlohy (Cancellation)

Task možno zrušiť pomocou task.cancel().

import asyncio

async def worker():
    try:
        while True:
            print(“Pracujem...”)
            await asyncio.sleep(1)
    except asyncio.CancelledError:
        print(“Task zrušený”)

async def main():
    task = asyncio.create_task(worker())

    await asyncio.sleep(3)
    task.cancel()

    await task

asyncio.run(main())

11. Spracovanie výnimiek

Výnimky fungujú rovnako ako pri synchronnom kóde.

async def divide(a, b):
    return a / b

async def main():
    try:
        result = await divide(10, 0)
    except ZeroDivisionError:
        print(“Delenie nulou”)

asyncio.run(main())

12. Semaphore – obmedzenie počtu úloh

Predstav si, že API povoľuje len 5 requestov naraz.

import asyncio

sem = asyncio.Semaphore(5)

async def fetch(url):
    async with sem:
        await asyncio.sleep(1)

Výhoda:

  • nezahltíš server
  • kontroluješ počet súbežných operácií

13. Async Queue

Na komunikáciu medzi taskmi slúži asyncio.Queue.

import asyncio

async def producer(queue):
    for i in range(5):
        await queue.put(i)

async def consumer(queue):
    while True:
        item = await queue.get()
        print(item)
        queue.task_done()

async def main():
    queue = asyncio.Queue()

    asyncio.create_task(consumer(queue))

    await producer(queue)
    await queue.join()

asyncio.run(main())

Použitie:

  • producer-consumer model
  • streamovanie dát
  • spracovanie správ

14. Async Context Manager

Tak ako existuje:

with open(“file.txt”) as f:

existuje aj:

async with session.get(url) as response:

Používa sa pri objektoch vyžadujúcich asynchrónne otvorenie alebo zatvorenie.

15. Async Iterátor

Bežný iterátor:

for item in data:
    ...

Asynchrónny iterátor:

async for item in stream:
    print(item)

Príklad:

async for line in websocket:
    print(line)

16. Async Generátor

Môžeš kombinovať yield a async.

async def generator():
    for i in range(5):
        await asyncio.sleep(1)
        yield i

Použitie:

async for item in generator():
    print(item)

17. Kedy async použiť?

✅ Vhodné:

  • API requesty (HTTP)
  • databázy
  • scraping
  • sieťová komunikácia

❌ Nevhodné:

  • CPU náročné úlohy (napr. AI, výpočty) → tam použi multiprocessing

Kedy async neprinesie zrýchlenie?

Tento kód nebude rýchlejší:

async def calculate():
    total = 0

    for i in range(10**9):
        total += i

Prečo?

  • nečaká na I/O
  • event loop nemá kedy prepnúť úlohy
  • CPU je neustále vyťažené

18. Kombinácia s Thread Pool

Ak musíš použiť blokujúcu funkciu:

import asyncio

def blocking():
    import time
    time.sleep(5)
    return 42

async def main():
    result = await asyncio.to_thread(blocking)
    print(result)

asyncio.run(main())

Výhoda:

  • event loop sa nezablokuje
  • starý synchronný kód môžeš používať aj v async aplikácii

19. Reálny príklad (HTTP requesty)

import aiohttp
import asyncio

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def main():
    urls = [
        “https://example.com”,
        “https://example.org”
    ]

    results = await asyncio.gather(*(fetch(url) for url in urls))
    print(len(results))

asyncio.run(main())

20. Async databázy

Moderné knižnice podporujú async:

PostgreSQL

import asyncpg

conn = await asyncpg.connect(...)

SQLAlchemy Async

from sqlalchemy.ext.asyncio import create_async_engine

engine = create_async_engine(DB_URL)

21. Architektúra modernej webovej aplikácie

Typický async backend:

Klient
   │
   ▼
FastAPI
   │
   ├── HTTP requesty
   ├── Databáza
   ├── Redis
   └── Message Queue

Všetko môže fungovať asynchrónne bez vytvárania tisícok vlákien.

22. Async frameworky

Najznámejšie:

FastAPI

@app.get(“/”)
async def root():
    return {“msg”: “hello”}

aiohttp

from aiohttp import web

Sanic

from sanic import Sanic

23. Najčastejšie použitie v praxi

  • Web API (FastAPI)
  • Chat aplikácie
  • Discord boty
  • Telegram boty
  • Web scraping
  • Mikroslužby
  • WebSocket servery
  • Streaming dát
  • Cloud aplikácie

24. Časté chyby

❌ Zabudnuté await

task1()  # nič sa nespustí!

❌ Blokujúci kód v async

time.sleep(2)  # ZLE

✔ správne:

await asyncio.sleep(2)

25. Async vs Threads

Feature Async Threads
Výkon (I/O) 🔥 vysoký dobrý
Pamäť nízka vyššia
CPU úlohy ❌ slabé ✔ lepšie
Zložitosť stredná vyššia

💡 Mentálny model

Predstav si čašníka:

  • synchronný: obsluhuje jeden stôl a čaká
  • async: obsluhuje viac stolov naraz

🎯 Finálne zhrnutie

Asynchrónne programovanie v Pythone je model založený na event loop-e, ktorý umožňuje efektívne spracovať veľké množstvo I/O operácií bez vytvárania veľkého počtu vlákien.

Zapamätaj si:

  1. async def vytvára coroutine.
  2. await neblokuje program.
  3. asyncio.gather() spúšťa viac úloh súčasne.
  4. create_task() spúšťa úlohy na pozadí.
  5. Async je ideálny pre siete, API a databázy.
  6. Pre CPU náročné úlohy použi multiprocessing.
  7. Async ≠ paralelizmus, ale efektívna konkurentnosť (concurrency).

Bonus: Ako event loop funguje pod kapotou?

Tu je minimálna implementácia vlastného event loopu — bez asyncio, len so select:

import select
import socket

class MiniEventLoop:
    def __init__(self):
        self.ready = []          # úlohy, ktoré sa môžu spustiť
        self.waiting = {}        # fd → úloha (čaká na I/O)
        self.sock = socket.socket()

    def create_task(self, coro):
        self.ready.append(coro)

    def add_reader(self, fd, coro, *args):
        self.waiting[fd] = (coro, args)

    def run(self):
        while self.ready or self.waiting:
            # 1) Spustíme pripravené úlohy
            while self.ready:
                coro = self.ready.pop(0)
                try:
                    coro.send(None)  # krok coroutiny
                except StopIteration:
                    pass

            # 2) Čakáme na I/O pomocou select()
            if self.waiting:
                fds = list(self.waiting.keys())
                readable, _, _ = select.select(fds, [], [])

                for fd in readable:
                    coro, args = self.waiting.pop(fd)
                    self.ready.append(coro)

loop = MiniEventLoop()

Čo to robí:

  1. Úlohy sa pridajú do fronty ready.
  2. Po každom kroku sa čaká na I/O cez select().
  3. Keď je socket čitateľný, príslušná úloha sa vráti do ready.

👉 To je podstatu celého event loopu — čakať na I/O a prepínať medzi úlohami.

Reload?