Asenkron Programlama (async/await)
Giriş: Neden Async?
Bir web sunucusu düşün. Aynı anda 1000 kullanıcı istek gönderiyor. Her istek için bir veritabanı sorgusu gerekiyor ve bu sorgu 100ms sürüyor. Eğer her isteği sırayla işlersen: 1000 × 100ms = 100 saniye. Threading ile 100 thread açsan bile bellek ve context switch maliyeti yüksek.
Peki ya tek bir thread ile 1000 isteği aynı anda idare edebilsen? İşte async programlama bunu yapıyor.
Analoji: Garson Modeli
En güçlü analoji garson analojisi:
Senkron garson: Bir masaya sipariş alır, mutfağa verir, yemek gelene kadar masanın başında bekler, yemeği servis eder, sonra diğer masaya geçer. 10 masa varsa uzuuun sürer.
Async garson: Bir masaya sipariş alır, mutfağa verir, hemen diğer masaya geçer. Mutfaktan yemek hazır sinyali gelince o masaya servis yapar. Tek garson, 20 masayı rahatça idare eder.
Senkron: Async:
Masa 1: sipariş→bekle→servis Masa 1: sipariş→...→servis
Masa 2: sipariş→...→servis
Masa 2: sipariş→bekle→servis Masa 3: sipariş→...→servis
Masa 4: sipariş→...→servis
Masa 3: sipariş→bekle→servis Async programlama, I/O bekleme süresini verimli kullanmak için tasarlanmıştır. Bekleme olduğunda kontrolü başka bir göreve devreder.
Event Loop Kavramı
Async programlamanın kalbi event loop'tur. Sürekli dönen bir döngü düşün:
Hazır olan görevleri çalıştır
I/O bekleyen görevleri beklet
I/O tamamlanan görevleri tekrar çalıştır
1'e dön
Event Loop:
┌─────────────────────────────────┐
│ 1. Hazır görev var mı? │
│ Evet → Çalıştır │
│ Hayır → I/O bekle │
│ 2. I/O tamamlandı mı? │
│ Evet → Görevi hazıra al │
│ 3. Tekrarla │
└─────────────────────────────────┘Event loop, tüm async operasyonları koordine eden "orkestra şefi"dir. Python'da asyncio modülü bu event loop'u sağlar.
async def ve await
async def: Coroutine Tanımlama
Normal fonksiyon def ile tanımlanır. Async fonksiyon async def ile tanımlanır:
import asyncio
# Normal fonksiyon
def normal_function():
return "Merhaba"
# Async fonksiyon (coroutine)
async def async_function():
return "Merhaba"
# Normal fonksiyon hemen sonuç döner
print(normal_function()) # "Merhaba"
# Async fonksiyon coroutine nesnesi döner
print(async_function()) # <coroutine object ...>
# Çalıştırmak için await veya asyncio.run() lazımawait: Async İşlemi Bekle
await keyword'ü, bir async işlemin tamamlanmasını bekler. Ama beklerken event loop'u bloklamaz — diğer görevler çalışabilir.
import asyncio
async def fetch_data(name, delay):
"""Veri çekmeyi simüle eder."""
print(f"⏳ {name}: Veri isteniyor...")
await asyncio.sleep(delay) # I/O bekleme simülasyonu
print(f"✅ {name}: Veri geldi! ({delay}s)")
return f"data_{name}"
async def main():
result = await fetch_data("API", 2)
print(f"Sonuç: {result}")
# Programı çalıştır
asyncio.run(main())Önemli: await sadece async def içinde kullanılabilir. Normal fonksiyondan await çağıramazsın.
asyncio.sleep() vs time.sleep()
import asyncio
import time
async def wrong_way():
"""❌ YANLIŞ: time.sleep event loop'u bloklar!"""
time.sleep(2) # TÜM program 2 saniye durur
return "data"
async def right_way():
"""✅ DOĞRU: asyncio.sleep event loop'u serbest bırakır."""
await asyncio.sleep(2) # Beklerken diğer görevler çalışır
return "data"⚠️ Dikkat: Async kodda asla
time.sleep()kullanma! Event loop'u bloklar ve tüm async avantajını yok eder. Her zamanawait asyncio.sleep()kullan.
asyncio.run() ile Başlatma
asyncio.run(), async programı başlatmanın en basit yolu:
import asyncio
async def greet(name):
await asyncio.sleep(1)
return f"Merhaba, {name}!"
# En basit kullanım
result = asyncio.run(greet("Ali"))
print(result) # Merhaba, Ali!asyncio.run() şunları yapar:
Yeni bir event loop oluşturur
Verilen coroutine'i çalıştırır
Tüm görevler bitince event loop'u kapatır
Not: asyncio.run() zaten çalışan bir event loop varsa hata verir. Jupyter notebook'ta bu sorunla karşılaşabilirsin — orada await direkt kullanabilirsin.
Coroutine Nedir?
Coroutine, async def ile tanımlanan fonksiyonun çağrılmasıyla oluşan nesnedir. Bir coroutine:
Çalıştırılabilir (runnable)
Duraklatılabilir (suspendable) —
awaitnoktalarındaDevam ettirilebilir (resumable)
import asyncio
async def my_coroutine():
print("Başladı")
await asyncio.sleep(1) # Burada duraklar
print("Devam etti")
await asyncio.sleep(1) # Burada tekrar duraklar
print("Bitti")
return 42
# Coroutine oluştur (henüz çalışmıyor)
coro = my_coroutine()
print(type(coro)) # <class 'coroutine'>
# Çalıştır
result = asyncio.run(coro)
print(result) # 42Coroutine'ler lazy — oluşturmak çalıştırmak demek değil. await veya asyncio.run() ile açıkça çalıştırman gerekir.
asyncio.gather(): Birden Fazla Task Paralel
asyncio.gather(), birden fazla coroutine'i aynı anda başlatır ve hepsinin bitmesini bekler. Bu, async'in asıl gücü!
import asyncio
import time
async def fetch_data(source, delay):
"""Farklı kaynaklardan veri çekmeyi simüle eder."""
print(f"⬇️ {source} isteniyor...")
await asyncio.sleep(delay)
print(f"✅ {source} geldi! ({delay}s)")
return f"data_from_{source}"
async def main_sequential():
"""Sıralı çalıştırma — yavaş."""
start = time.perf_counter()
r1 = await fetch_data("API", 2)
r2 = await fetch_data("Database", 3)
r3 = await fetch_data("Cache", 1)
elapsed = time.perf_counter() - start
print(f"Sıralı: {elapsed:.1f}s") # ~6s
return [r1, r2, r3]
async def main_parallel():
"""Paralel çalıştırma — hızlı!"""
start = time.perf_counter()
results = await asyncio.gather(
fetch_data("API", 2),
fetch_data("Database", 3),
fetch_data("Cache", 1),
)
elapsed = time.perf_counter() - start
print(f"Paralel: {elapsed:.1f}s") # ~3s (en uzun süre)
return results
# Farkı gör
asyncio.run(main_sequential()) # ~6 saniye
asyncio.run(main_parallel()) # ~3 saniye6 saniye → 3 saniye! gather() tüm coroutine'leri aynı anda başlatıyor, en uzun sürenin tamamlanmasını bekliyor.
gather() ile Hata Yönetimi
async def risky_task(name, should_fail=False):
await asyncio.sleep(1)
if should_fail:
raise ValueError(f"{name} başarısız oldu!")
return f"{name} başarılı"
async def main():
# return_exceptions=True ile hatalar sonuç olarak döner
results = await asyncio.gather(
risky_task("A"),
risky_task("B", should_fail=True),
risky_task("C"),
return_exceptions=True
)
for result in results:
if isinstance(result, Exception):
print(f"❌ Hata: {result}")
else:
print(f"✅ {result}")
asyncio.run(main())
# ✅ A başarılı
# ❌ Hata: B başarısız oldu!
# ✅ C başarılıasyncio.create_task()
create_task(), bir coroutine'i hemen arka planda başlatır. await ile sonucunu istediğin zaman alabilirsin.
import asyncio
async def background_job(name, duration):
print(f"🔄 {name} arka planda başladı")
await asyncio.sleep(duration)
print(f"✅ {name} tamamlandı")
return f"result_{name}"
async def main():
# Task'ları oluştur — hemen başlar!
task1 = asyncio.create_task(background_job("İndirme", 3))
task2 = asyncio.create_task(background_job("İşleme", 2))
# Bu arada başka iş yap
print("📋 Ana iş yapılıyor...")
await asyncio.sleep(1)
print("📋 Ana iş devam ediyor...")
# Sonuçları al
result1 = await task1
result2 = await task2
print(f"Sonuçlar: {result1}, {result2}")
asyncio.run(main())create_task vs await
async def main():
# Bu SIRALI çalışır (her biri bitene kadar bekler)
result1 = await slow_function(2)
result2 = await slow_function(3)
# Toplam: ~5 saniye
# Bu PARALEL çalışır
task1 = asyncio.create_task(slow_function(2))
task2 = asyncio.create_task(slow_function(3))
result1 = await task1
result2 = await task2
# Toplam: ~3 saniyeTask İptal Etme
async def long_running():
try:
while True:
print("Çalışıyor...")
await asyncio.sleep(1)
except asyncio.CancelledError:
print("İptal edildi! Temizlik yapılıyor...")
raise # CancelledError'ı yeniden fırlat
async def main():
task = asyncio.create_task(long_running())
await asyncio.sleep(3)
task.cancel() # Task'ı iptal et
try:
await task
except asyncio.CancelledError:
print("Task başarıyla iptal edildi")
asyncio.run(main())asyncio.wait() ve asyncio.wait_for()
wait(): Daha Fazla Kontrol
import asyncio
async def task(name, duration):
await asyncio.sleep(duration)
return f"{name}:{duration}s"
async def main():
tasks = [
asyncio.create_task(task("A", 1)),
asyncio.create_task(task("B", 3)),
asyncio.create_task(task("C", 2)),
]
# İlk biten task'ı al
done, pending = await asyncio.wait(
tasks, return_when=asyncio.FIRST_COMPLETED
)
for t in done:
print(f"✅ Bitti: {t.result()}")
print(f"⏳ Bekleyen: {len(pending)} task")
# Kalanları da bekle
done2, _ = await asyncio.wait(pending)
for t in done2:
print(f"✅ Bitti: {t.result()}")
asyncio.run(main())wait_for(): Timeout ile Bekleme
async def slow_operation():
await asyncio.sleep(10)
return "Tamamlandı"
async def main():
try:
result = await asyncio.wait_for(slow_operation(), timeout=3.0)
print(result)
except asyncio.TimeoutError:
print("⏰ Zaman aşımı! 3 saniyede tamamlanamadı.")
asyncio.run(main())async for ve async with
Async Iterator (async for)
Normal iterator'ın async versiyonu. __aiter__ ve __anext__ metodları kullanır.
import asyncio
class AsyncCounter:
"""Async sayaç — her adımda bekleme var."""
def __init__(self, start, stop):
self.current = start
self.stop = stop
def __aiter__(self):
return self
async def __anext__(self):
if self.current >= self.stop:
raise StopAsyncIteration
await asyncio.sleep(0.5) # Async bekleme
value = self.current
self.current += 1
return value
async def main():
async for num in AsyncCounter(1, 6):
print(f"Sayı: {num}")
asyncio.run(main())Async Generator
async def async_range(start, stop):
"""Async generator — yield ile."""
for i in range(start, stop):
await asyncio.sleep(0.3)
yield i
async def main():
async for num in async_range(1, 6):
print(num, end=" ")
# 1 2 3 4 5
asyncio.run(main())Async Context Manager (async with)
import asyncio
class AsyncDatabase:
"""Async veritabanı bağlantısı."""
async def __aenter__(self):
print("🔌 Async bağlantı kuruluyor...")
await asyncio.sleep(1) # Bağlantı kurma süresi
print("✅ Bağlantı kuruldu")
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("🔌 Async bağlantı kapatılıyor...")
await asyncio.sleep(0.5)
print("✅ Bağlantı kapatıldı")
return False
async def query(self, sql):
await asyncio.sleep(0.5)
return [{"id": 1, "name": "Ali"}]
async def main():
async with AsyncDatabase() as db:
results = await db.query("SELECT * FROM users")
print(f"Sonuçlar: {results}")
asyncio.run(main())aiohttp: Async HTTP İstekleri
Gerçek dünyada async'in en yaygın kullanımı HTTP istekleri. aiohttp kütüphanesi bunu sağlar.
# pip install aiohttp
import asyncio
import aiohttp
import time
async def fetch_url(session, url):
"""Tek bir URL'den veri çek."""
async with session.get(url) as response:
data = await response.text()
return {
"url": url,
"status": response.status,
"size": len(data)
}
async def fetch_all(urls):
"""Birden fazla URL'yi paralel çek."""
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
async def main():
urls = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/3",
]
start = time.perf_counter()
results = await fetch_all(urls)
elapsed = time.perf_counter() - start
for result in results:
if isinstance(result, Exception):
print(f"❌ Hata: {result}")
else:
print(f"✅ {result['url']} → {result['status']} ({result['size']} bytes)")
print(f"\n⏱️ Toplam süre: {elapsed:.1f}s")
# Sıralı olsa ~7s, async ile ~3s
# asyncio.run(main())requests kütüphanesi senkrondur — async kodda kullanma. Async HTTP için aiohttp veya httpx kullan.
Semaphore: Eşzamanlılık Limiti
Bazen tüm görevleri aynı anda çalıştırmak istemezsin (rate limiting, sunucu koruması). Semaphore bunu sağlar.
import asyncio
async def fetch_with_limit(sem, url, delay):
"""Semaphore ile sınırlı eşzamanlı istek."""
async with sem: # En fazla N eşzamanlı istek
print(f"⬇️ {url} başladı")
await asyncio.sleep(delay)
print(f"✅ {url} bitti")
return f"data_{url}"
async def main():
sem = asyncio.Semaphore(3) # En fazla 3 eşzamanlı
tasks = [
fetch_with_limit(sem, f"url_{i}", 1)
for i in range(10)
]
results = await asyncio.gather(*tasks)
print(f"Toplam: {len(results)} sonuç")
asyncio.run(main())Sync vs Async vs Threading: Karşılaştırma
| Özellik | Sync | Threading | Async |
|---|---|---|---|
| Eşzamanlılık | ❌ | ✅ | ✅ |
| GIL etkisi | - | Var | Yok (tek thread) |
| Bellek kullanımı | Düşük | Yüksek | Düşük |
| Context switch | Yok | OS seviye (pahalı) | Uygulama seviye (ucuz) |
| 10K bağlantı | ❌ İmkansız | ⚠️ Zor | ✅ Kolay |
| Kod karmaşıklığı | Basit | Orta (lock, race) | Orta (async/await) |
| Debug kolaylığı | Kolay | Zor | Orta |
| Uygun iş tipi | Basit scriptler | I/O-bound | Çok sayıda I/O |
| CPU-bound | ✅ | ❌ (GIL) | ❌ |
Performans Karşılaştırması (I/O-bound)
import asyncio
import threading
import time
# 100 adet 0.1s'lik I/O işlemi simülasyonu
# Senkron
def sync_version():
for _ in range(100):
time.sleep(0.1)
# Threading
def threading_version():
threads = []
for _ in range(100):
t = threading.Thread(target=time.sleep, args=(0.1,))
threads.append(t)
t.start()
for t in threads:
t.join()
# Async
async def async_version():
tasks = [asyncio.sleep(0.1) for _ in range(100)]
await asyncio.gather(*tasks)
# Sonuçlar (yaklaşık):
# Sync: ~10.0s
# Threading: ~ 0.2s (ama 100 thread!)
# Async: ~ 0.1s (tek thread, minimum bellek)Ne Zaman Async Kullanmalı?
✅ Async Kullan
Çok sayıda I/O işlemi (HTTP, veritabanı, dosya)
Web sunucuları (FastAPI, Sanic)
WebSocket uygulamaları
Chat botları
Web scraping (çok sayıda sayfa)
Microservice iletişimi
❌ Async Kullanma
CPU-bound işler (hesaplama, resim işleme) →
multiprocessingBasit scriptler (async gereksiz karmaşıklık)
Senkron kütüphaneler kullanıyorsan (requests, sqlite3)
Tek seferlik I/O işlemi
Karar Ağacı
İşin türü ne?
├── CPU-bound → multiprocessing
├── I/O-bound
│ ├── Az sayıda I/O (< 10) → threading veya sync
│ ├── Çok sayıda I/O (> 100) → asyncio
│ └── Web sunucusu → asyncio (FastAPI)
└── Basit script → sync💡 İpucu: "Async her zaman daha iyidir" diye düşünme. Basit işler için senkron kod daha okunur ve debug'ı kolaydır. Async'i gerçekten ihtiyaç duyduğunda kullan.
Pratik: Async Web Scraper
import asyncio
import aiohttp
from html.parser import HTMLParser
class TitleParser(HTMLParser):
def __init__(self):
super().__init__()
self.in_title = False
self.title = ""
def handle_starttag(self, tag, attrs):
if tag == "title":
self.in_title = True
def handle_data(self, data):
if self.in_title:
self.title += data
def handle_endtag(self, tag):
if tag == "title":
self.in_title = False
async def get_title(session, url):
"""Web sayfasının başlığını al."""
try:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
html = await resp.text()
parser = TitleParser()
parser.feed(html)
return {"url": url, "title": parser.title.strip(), "status": resp.status}
except Exception as e:
return {"url": url, "title": None, "error": str(e)}
async def scrape_titles(urls, max_concurrent=5):
"""Birden fazla sayfanın başlığını paralel al."""
sem = asyncio.Semaphore(max_concurrent)
async def limited_get(session, url):
async with sem:
return await get_title(session, url)
async with aiohttp.ClientSession() as session:
tasks = [limited_get(session, url) for url in urls]
return await asyncio.gather(*tasks)
async def main():
urls = [
"https://python.org",
"https://github.com",
"https://stackoverflow.com",
"https://docs.python.org",
"https://pypi.org",
]
results = await scrape_titles(urls)
for r in results:
if "error" in r:
print(f"❌ {r['url']}: {r['error']}")
else:
print(f"✅ {r['url']}: {r['title']}")
# asyncio.run(main())Async Pattern'ler
Producer-Consumer (Async Queue)
import asyncio
async def producer(queue, n):
"""Kuyruğa veri ekle."""
for i in range(n):
item = f"item_{i}"
await queue.put(item)
print(f"📦 Üretildi: {item}")
await asyncio.sleep(0.1)
await queue.put(None) # Sentinel
async def consumer(queue, name):
"""Kuyruktan veri al ve işle."""
while True:
item = await queue.get()
if item is None:
await queue.put(None) # Diğer consumer'lar için
break
print(f"🔧 {name} işledi: {item}")
await asyncio.sleep(0.3)
async def main():
queue = asyncio.Queue(maxsize=5)
await asyncio.gather(
producer(queue, 10),
consumer(queue, "Worker-1"),
consumer(queue, "Worker-2"),
)
asyncio.run(main())Retry Pattern
import asyncio
import random
async def unreliable_api():
"""Bazen başarısız olan API çağrısı."""
if random.random() < 0.7: # %70 başarısızlık
raise ConnectionError("API yanıt vermedi")
return {"status": "ok", "data": 42}
async def retry(coro_func, max_retries=3, delay=1.0):
"""Başarısız olursa tekrar dene."""
for attempt in range(1, max_retries + 1):
try:
result = await coro_func()
print(f"✅ Başarılı (deneme {attempt})")
return result
except Exception as e:
print(f"⚠️ Deneme {attempt}/{max_retries}: {e}")
if attempt < max_retries:
wait = delay * (2 ** (attempt - 1)) # Exponential backoff
print(f" {wait}s sonra tekrar denenecek...")
await asyncio.sleep(wait)
raise RuntimeError(f"{max_retries} denemede de başarısız oldu")
async def main():
try:
result = await retry(unreliable_api, max_retries=5, delay=0.5)
print(f"Sonuç: {result}")
except RuntimeError as e:
print(f"❌ {e}")
asyncio.run(main())Yaygın Hatalar
1. Coroutine'i await Etmemek
async def fetch():
return "data"
async def main():
# ❌ YANLIŞ: Coroutine çalışmaz
result = fetch() # RuntimeWarning: coroutine was never awaited
# ✅ DOĞRU
result = await fetch()2. Senkron Fonksiyon Çağırmak
async def main():
# ❌ YANLIŞ: Event loop'u bloklar
import requests
response = requests.get("https://api.example.com")
# ✅ DOĞRU: Async kütüphane kullan
async with aiohttp.ClientSession() as session:
async with session.get("https://api.example.com") as response:
data = await response.json()
# Veya senkron kodu thread'e delege et
loop = asyncio.get_event_loop()
response = await loop.run_in_executor(
None, requests.get, "https://api.example.com"
)3. Async Olmayan Fonksiyonda await Kullanmak
# ❌ SyntaxError
def normal_function():
await asyncio.sleep(1) # SyntaxError!
# ✅ async def olmalı
async def async_function():
await asyncio.sleep(1) # DoğruÖzet
Async programlama, I/O bekleme süresini verimli kullanarak tek thread ile binlerce eşzamanlı işlem yönetmeyi sağlar. Garson analojisi: bir garson, birçok masa.
`async def` ile coroutine tanımlanır, `await` ile çalıştırılır.
asyncio.run()ile program başlatılır.`asyncio.gather()` birden fazla coroutine'i aynı anda çalıştırır. `asyncio.create_task()` ise arka planda başlatır.
`async for` ve `async with`, sırasıyla async iterator ve async context manager için kullanılır.
Sync vs Threading vs Async: Basit işler → sync, az I/O → threading, çok I/O → async, CPU-bound → multiprocessing.
Async kodda senkron bloklamadan kaçın (
time.sleep,requests). Async alternatifleri kullan (asyncio.sleep,aiohttp).
AI Asistan
Sorularını yanıtlamaya hazır