Python'un asenkron programlama modeli, I/O-bound işlemlerde performansı dramatik biçimde artırır. Bir web scraper 100 URL çekecekse, senkron kodla 100 × 0.5s = 50 saniye beklersin; async ile aynı 100 istek 0.5–1 saniye sürer. Bu makalede asyncio, async/await sözdizimi ve pratik kullanım kalıplarını öğreneceksin.
Senkron vs Asenkron: Neden Fark Var?
// PYTHON //
# Senkron — sıralı beklemeimport timedef get_data(url): time.sleep(0.5) # ağ gecikmesi simülasyonu return f"Veri: {url}"# 10 URL için → 5 saniyeurls = [f"https://api.example.com/{i}" for i in range(10)]results = [get_data(u) for u in urls]
// PYTHON //
# Asenkron — eşzamanlı beklemeimport asyncioimport httpxasync def get_data(client, url): response = await client.get(url) return response.json()async def main(): async with httpx.AsyncClient() as client: tasks = [get_data(client, url) for url in urls] results = await asyncio.gather(*tasks)# 10 URL için → ~0.5 saniyeasyncio.run(main())
Temel Kavramlar
Coroutine
async def ile tanımlanan fonksiyon bir coroutine'dir. Çağrıldığında çalışmaz, sadece bir coroutine nesnesi döner. await ile çalıştırılır.
// PYTHON //
async def merhaba(): print("Merhaba") await asyncio.sleep(1) print("Dünya")# Yanlış: doğrudan çağırmak işe yaramaz# merhaba() → <coroutine object merhaba at 0x...># Doğru: await veya asyncio.run ile çalıştırasyncio.run(merhaba())
Event Loop
Tüm coroutine'leri koordine eden merkezi döngü. asyncio.run() bu döngüyü başlatır ve coroutine tamamlandığında kapatır.
// PYTHON //
import asyncioasync def task(isim, sure): print(f"{isim} başladı") await asyncio.sleep(sure) # ← burada loop diğer task'lara geçer print(f"{isim} bitti ({sure}s)")async def main(): # Tüm task'lar eşzamanlı çalışır await asyncio.gather( task("A", 2), task("B", 1), task("C", 3), ) # Toplam süre: max(2,1,3) = 3s, 6s değilasyncio.run(main())# B başladı, A başladı, C başladı# B bitti (1s)# A bitti (2s)# C bitti (3s)
asyncio.gather vs asyncio.create_task
// PYTHON //
# gather — coroutine'leri alır, hepsini beklerresults = await asyncio.gather(coro1(), coro2(), coro3())# create_task — arka planda başlatır, sonuç sonra alınırasync def main(): task1 = asyncio.create_task(coro1()) task2 = asyncio.create_task(coro2()) # ... başka işler yap ... result1 = await task1 result2 = await task2
// PYTHON //
# return_exceptions=True — tek hata tüm grubu kesmezresults = await asyncio.gather( fetch_url("https://example.com"), fetch_url("https://hatalı.invalid"), return_exceptions=True,)for r in results: if isinstance(r, Exception): print(f"Hata: {r}") else: print(f"Veri: {r[:50]}")
Gerçek Dünya: Async HTTP İstekleri
// PYTHON //
import asyncioimport httpxfrom typing import Anyasync def fetch_json(client: httpx.AsyncClient, url: str) -> dict[str, Any]: try: response = await client.get(url, timeout=10.0) response.raise_for_status() return response.json() except httpx.TimeoutException: return {"error": "timeout", "url": url} except httpx.HTTPStatusError as e: return {"error": e.response.status_code, "url": url}async def fetch_all(urls: list[str]) -> list[dict]: limits = httpx.Limits(max_connections=20, max_keepalive_connections=10) async with httpx.AsyncClient(limits=limits) as client: tasks = [fetch_json(client, url) for url in urls] return await asyncio.gather(*tasks, return_exceptions=True)# 100 URL'yi eşzamanlı çekurls = [f"https://jsonplaceholder.typicode.com/posts/{i}" for i in range(1, 101)]results = asyncio.run(fetch_all(urls))print(f"{len(results)} sonuç alındı")
asyncio.Semaphore: Eşzamanlılığı Sınırla
// PYTHON //
# Sunucuyu aşırı yüklememek için eşzamanlı istek sayısını sınırlaasync def fetch_limited(sem: asyncio.Semaphore, client: httpx.AsyncClient, url: str): async with sem: # maksimum 10 eşzamanlı istek return await fetch_json(client, url)async def main(): sem = asyncio.Semaphore(10) async with httpx.AsyncClient() as client: tasks = [fetch_limited(sem, client, url) for url in urls] return await asyncio.gather(*tasks)
Async Context Manager ve Generator
// PYTHON //
# Async context managerclass AsyncDatabaseConnection: async def __aenter__(self): self.conn = await asyncpg.connect(DATABASE_URL) return self.conn async def __aexit__(self, *args): await self.conn.close()async def get_users(): async with AsyncDatabaseConnection() as conn: return await conn.fetch("SELECT * FROM users")# Async generator — büyük veriyi parça parça işleasync def read_lines(dosya: str): async with aiofiles.open(dosya, 'r', encoding='utf-8') as f: async for line in f: yield line.strip()async def main(): async for satir in read_lines("buyuk_dosya.txt"): await process(satir) # her satırı async işle
asyncio.Queue: Producer-Consumer
// PYTHON //
async def producer(queue: asyncio.Queue, urls: list[str]): for url in urls: await queue.put(url) # Tüketici'ye "bitti" sinyali await queue.put(None)async def consumer(queue: asyncio.Queue, worker_id: int): async with httpx.AsyncClient() as client: while True: url = await queue.get() if url is None: break data = await fetch_json(client, url) print(f"Worker-{worker_id}: {url}") queue.task_done()async def main(): queue = asyncio.Queue(maxsize=50) # 3 tüketici çalışsın workers = [asyncio.create_task(consumer(queue, i)) for i in range(3)] await producer(queue, urls) await queue.join() for w in workers: w.cancel()
Yaygın Hatalar
// PYTHON //
# YANLIŞ: blocking çağrı event loop'u durdururasync def yanlis(): time.sleep(1) # tüm loop durur! requests.get(url) # sync HTTP — loop bloklanır# DOĞRU: async eşdeğerlerini kullanasync def dogru(): await asyncio.sleep(1) async with httpx.AsyncClient() as c: await c.get(url)# YANLIŞ: asyncio.run içinde asyncio.run çağırmak# RuntimeError: This event loop is already running# DOĞRU: nest_asyncio (Jupyter için) veya farklı yapıimport nest_asyncionest_asyncio.apply() # Jupyter Notebook'ta gerekli
FastAPI ile Entegrasyon
// PYTHON //
from fastapi import FastAPIimport asyncpgapp = FastAPI()pool: asyncpg.Pool | None = None@app.on_event("startup")async def startup(): global pool pool = await asyncpg.create_pool(DATABASE_URL, min_size=5, max_size=20)@app.get("/users")async def get_users(): async with pool.acquire() as conn: rows = await conn.fetch("SELECT id, name, email FROM users") return [dict(r) for r in rows]
Özet
Python async programlamanın özü şu: await bir I/O işlemini beklerken event loop başka coroutine'lere geçer, CPU boşa gitmiyor. asyncio.gather paralel I/O için, Semaphore eşzamanlılık sınırı için, Queue üretici-tüketici deseni için kullanılır. time.sleep yerine asyncio.sleep, requests yerine httpx.AsyncClient — bu iki değişiklik yeterli.