Rehberler Uzun Okuma Async Await

Async Python nasıl çalışır?

Event loop, coroutine yaşam döngüsü ve paralel I/O desenleri.

9 Ocak 2026 20 dk okuma
Paylaş
X in

Giriş

asyncio, Python'da tek iş parçacıklı (single-threaded) eşzamanlılık sağlayan standart kütüphanedir. Ağ istekleri, veritabanı sorguları ve dosya I/O gibi beklemeye dayalı işlerde thread kullanmadan binlerce bağlantıyı verimli yönetmenizi sağlar. Ancak asyncio'nun gücü, doğru event loop modelini anlamaktan gelir.

Bu rehberde coroutine'lerin nasıl çalıştığını, event loop'un görevleri nasıl zamanladığını, Task ve Future soyutlamalarını ve asyncio.gather ile toplu iş yürütmeyi inceliyoruz. Yanlış kullanımda asyncio, thread'den bile yavaş olabilir; doğru desenlerle ise kaynak tüketimini dramatik biçimde düşürür.

Coroutine ve async/await Temelleri

async def ile tanımlanan fonksiyonlar çağrıldığında coroutine nesnesi döndürür; kendiliğinden çalışmaz. await ifadesi, başka bir coroutine'in tamamlanmasını beklerken kontrolü event loop'a geri verir. Bu cooperative multitasking modeli, CPU'yu meşgul etmeden I/O beklerken başka görevlere geçiş yapılmasını sağlar.

Senkron fonksiyon içinde await kullanamazsınız; asyncio.run() veya mevcut bir loop üzerinde run_until_complete gerekir. Karışık kod tabanlarında sync_to_async ve async_to_sync köprüleri, Django ve FastAPI gibi framework'lerde yaygındır.

import asyncio

async def fetch_data(url: str) -> dict:
    await asyncio.sleep(0.1)  # I/O simülasyonu
    return {"url": url, "status": 200}

async def main():
    result = await fetch_data("https://api.example.com")
    print(result)

asyncio.run(main())

Event Loop Mimarisi

Event loop, hazır coroutine'leri kuyruğa alır, I/O tamamlandığında callback'leri tetikler ve zamanlayıcıları yönetir. asyncio.run() her çağrıda yeni bir loop oluşturur ve iş bitince kapatır; uzun ömürlü uygulamalarda loop'u açık tutmak ve asyncio.create_task ile arka plan görevleri başlatmak daha uygundur.

get_event_loop() eski API'dir; Python 3.10+ ile asyncio.get_running_loop() tercih edilir. Loop'un kendi thread'inde çalışması gereken senaryolarda loop.run_in_executor, bloklayan CPU veya sync I/O işlerini thread pool'a devreder.

  • asyncio.run(): script giriş noktası için ideal
  • create_task(): arka plan coroutine başlatma
  • run_in_executor(): bloklayan sync kodu izole etme
  • call_soon(): callback zamanlama

Task, Future ve gather

asyncio.create_task bir coroutine'i hemen zamanlar ve Task nesnesi döndürür. Task, Future'ın alt sınıfıdır ve iptal (cancel), exception yakalama ve await ile sonuç bekleme destekler. Birden fazla bağımsız I/O işini paralel yürütmek için asyncio.gather kullanılır.

gather(*aws, return_exceptions=True) ile bir görev hata verse bile diğerleri tamamlanır; hatalar sonuç listesinde exception olarak döner. Bu, kısmi başarısızlığın kabul edilebilir olduğu toplu API çağrılarında kritiktir.

async def main():
    tasks = [
        asyncio.create_task(fetch_data(f"/users/{i}"))
        for i in range(5)
    ]
    results = await asyncio.gather(*tasks)
    print(results)

Bloklayan Koddan Kaçınma

time.sleep(), requests.get() veya senkron veritabanı sürücüleri event loop'u bloke eder; tüm async görevler durur. Çözüm: aiohttp, httpx (async mod), asyncpg, aiomysql gibi native async kütüphaneler veya run_in_executor ile thread havuzu.

CPU yoğun işler asyncio ile paralelleşmez; bunun için multiprocessing veya ProcessPoolExecutor gerekir. asyncio + multiprocessing hibrit modeller, web sunucularında I/O ve CPU işlerini ayrı katmanlarda yönetmek için kullanılır.

Async kodda bloklayan çağrı yapmayın; tüm event loop'u dondurursunuz.

Timeout, İptal ve Hata Yönetimi

asyncio.wait_for(coro, timeout=5.0) belirli sürede tamamlanmayan işleri iptal eder. task.cancel() CancellationError fırlatır; coroutine içinde try/finally ile kaynak temizliği yapılmalıdır. asyncio.TaskGroup (3.11+) structured concurrency sağlar: bir alt görev hata verirse diğerleri de iptal edilir.

Exception grupları ve except* sözdizimi (3.11+), birden fazla Task hatasını tek except bloğunda ayırmayı kolaylaştırır. Üretimde her dış API çağrısına timeout ve retry politikası eklemek, askıda kalan coroutine'leri önler.

async def with_timeout():
    try:
        return await asyncio.wait_for(fetch_data("/slow"), timeout=2.0)
    except asyncio.TimeoutError:
        return {"error": "timeout"}

Gerçek Dünya Desenleri

FastAPI ve Starlette, ASGI üzerinde asyncio ile native çalışır. Connection pool'lar (asyncpg, redis.asyncio) loop başına paylaşılır. Graceful shutdown için signal handler'da task'ları cancel edip loop'un bitmesini bekleyin.

Yük testlerinde asyncio ile binlerce eşzamanlı istek simüle edilebilir; ancak dosya tanımlayıcı limitleri (ulimit) ve bağlantı havuzu boyutları darboğaz olabilir. Semaphore ile eşzamanlılık üst sınırı koymak, kaynak tükenmesini engeller.

  1. I/O bound işler için asyncio tercih edin
  2. CPU bound işler için multiprocessing kullanın
  3. Her dış çağrıya timeout ekleyin
  4. Semaphore ile eşzamanlılığı sınırlayın

Sonuç

asyncio, I/O ağırlıklı Python servislerinde thread'e göre daha az bellek ve daha iyi ölçeklenebilirlik sunar. Event loop, coroutine ve gather modelini içselleştirdikten sonra FastAPI, aiohttp ve async veritabanı sürücüleriyle üretim kalitesinde servisler yazabilirsiniz.

Bloklayan kodu async dünyaya taşırken adım adım ilerleyin: önce I/O katmanını değiştirin, sonra iş mantığını coroutine'e dönüştürün. Profiling ile loop'un ne kadar süre boşta beklediğini ölçmek, optimizasyon önceliklerini netleştirir.