← Kursa Dön
📄 Text · 18 min

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:

  1. Hazır olan görevleri çalıştır

  2. I/O bekleyen görevleri beklet

  3. I/O tamamlanan görevleri tekrar çalıştır

  4. 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ım

await: 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 zaman await 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:

  1. Yeni bir event loop oluşturur

  2. Verilen coroutine'i çalıştırır

  3. 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) — await noktalarında

  • Devam 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)  # 42

Coroutine'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 saniye

6 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 saniye

Task İ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

ÖzellikSyncThreadingAsync
Eşzamanlılık
GIL etkisi-VarYok (tek thread)
Bellek kullanımıDüşükYüksekDüşük
Context switchYokOS seviye (pahalı)Uygulama seviye (ucuz)
10K bağlantı❌ İmkansız⚠️ Zor✅ Kolay
Kod karmaşıklığıBasitOrta (lock, race)Orta (async/await)
Debug kolaylığıKolayZorOrta
Uygun iş tipiBasit scriptlerI/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) → multiprocessing

  • Basit 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).