Python Asyncio ile Asenkron Programlama
Asenkron programlama, tek bir iş parçacığında birçok işi bekleme sürelerini birleştirerek yürütmenin yolu. Ağdan veri çekiyorsanız, yüzlerce dosyayı okuyorsanya da bir API'yi arka arkaya çağırıyorsanız zamanınızın çoğu beklemekle geçer. Python'da bu bekleme süresini işe çevirmenin standart aracı asyncio. python async await ikilisini kavradığınızda, aynı işi kat kat kısa sürede bitiren kod yazabilirsiniz.
Aşağıdaki adımları kendi terminalinizde denemeniz için tasarladım. Python 3.11 veya üstü varsayıyorum; sürümünüzü python3 --version ile doğrulayın.
Temeller: Coroutine, Event Loop ve await
Asyncio'yu anlamanın önündeki en büyük engel terimler. Üçünü oturttuğunuzda geri kalanı kolay.
Üç kavram, üç cümle
- Coroutine: async def ile tanımlanan fonksiyon. Çağırdığınızda çalışmaz, sadece bir nesne döner.
- Event loop: Coroutine'leri sırayla ilerleten zamanlayıcı. asyncio.run() bunu sizin için kurar ve kapatır.
- Task: Event loop'a "bunu arka planda başlat" demenin yolu. Eşzamanlılık tam olarak burada doğar.
Kritik nokta: async def yazmak kodunuzu hızlandırmaz. Hız, birden fazla coroutine'i aynı anda beklemeye aldığınızda gelir.
İlk çalışan örnek
Bir dosya açın, şu satırları yazın:
- import asyncio
- async def bekle(ad, saniye):
- await asyncio.sleep(saniye)
- print(ad, "bitti")
- async def main():
- await bekle("a", 2)
- await bekle("b", 2)
- asyncio.run(main())
Bu kod 4 saniye sürer. Çünkü iki await sırayla çalışıyor. Şimdi main içindeki iki satırı tek satıra indirin: await asyncio.gather(bekle("a", 2), bekle("b", 2)). Süre 2 saniyeye düşer. Aradaki fark, asyncio'nun tüm mantığı.
await neyi bekler, neyi beklemez
await yalnızca "awaitable" nesnelerle çalışır: coroutine, Task, Future. Ve yalnızca async def gövdesi içinde kullanılabilir.
| Durum | Asyncio kazanç sağlar mı? |
|---|---|
| Çok sayıda HTTP isteği | Evet, belirgin şekilde |
| Veritabanı sorguları (async sürücü ile) | Evet |
| Görüntü işleme, büyük hesaplama | Hayır, CPU'ya bağlı iş |
| time.sleep() ile bekleme | Hayır, loop'u tamamen dondurur |
Kural: iş beklemekten oluşuyorsa asyncio; iş hesaplamaktan oluşuyorsa multiprocessing ya da C tarafına inen kütüphaneler.
Pratikte Kullanım: Eşzamanlılık, Hata ve Bloklama
Temeli kurduktan sonra gerçek projede karşılaşacağınız üç konu var. Hepsini tek tek uygulayın.
Çok sayıda işi birlikte yürütmek
İki yaygın yol:
- asyncio.gather: Sonuçları giriş sırasına göre liste olarak döndürür. Kısa ve pratik.
- asyncio.TaskGroup: async with bloğu içinde tg.create_task() ile görev açarsınız. Bir görev hata verirse diğerleri iptal edilir. Modern kodda tercih edilen yaklaşım.
Eşzamanlı iş sayısını sınırlamayı unutmayın. 5000 isteği aynı anda açarsanız hedef sunucu da işletim sisteminiz de zorlanır. Çözüm asyncio.Semaphore: nesneyi bir kez oluşturun, her görev içinde async with sem: bloğuna girin. 20–50 arası bir limit çoğu iş için yeterli.
Zaman aşımı, iptal ve hatalar
- Her ağ çağrısına süre sınırı koyun: async with asyncio.timeout(5): bloğu içinde çalıştırın.
- Süre aşılırsa TimeoutError yükselir. Yakalayın, tekrar denemeyi geri çekilme (backoff) ile yapın.
- İptal edilen bir görev CancelledError alır. Bunu yutmayın; temizlik yapıp yeniden yükseltin.
- gather kullanıyorsanız return_exceptions=True ile hataları sonuç listesinde toplayabilirsiniz. Böylece tek başarısız istek tüm işi düşürmez.
Bir de klasik tuzak: create_task() ile başlattığınız görevin referansını tutmazsanız çöp toplayıcı onu sessizce silebilir. Görevleri bir listede veya küme içinde saklayın.
Senkron kodu event loop'u dondurmadan çalıştırmak
Elinizdeki kütüphane async değilse ne olacak? Örneğin klasik bir requests çağrısı ya da ağır bir dosya işlemi. Bun