Python’da Asyncio: Beklerken Diğer İşleri Yapan Kod Nasıl Yazılır?

Üç farklı sunucudan veri çekmeniz gerektiğini düşünün; her biri birkaç saniye sürüyor. Sırayla isteyip her birinin bitmesini beklerseniz toplam süre üçünün toplamı kadar uzar. Ya aynı anda, tek bir iş parçacığıyla (thread) hepsini beklemenin bir yolu olsaydı? İşte tam bu noktada Python’ın asyncio modülü devreye giriyor.

Bu Proje Ne Yapıyor?

Bu projede, üç farklı “sunucudan” veri çekme işlemini simüle eden asenkron bir program yazıyoruz. simulate_network_fetch adlı fonksiyon, her çağrıldığında belli bir süre “bekliyormuş” gibi davranıyor (3, 2 ve 1 saniye) ve ardından sahte bir yanıt döndürüyor.

Programın en çarpıcı yanı, bu üç isteğin eş zamanlı (concurrent) olarak başlatılması. Kod önce en uzun süren görevi (3 saniye) planlıyor, sonra 2 saniyelik, en son da 1 saniyelik görevi. Normalde sırayla çalıştırılsaydı toplam 6 saniye sürerdi. Ama çıktıyı incelediğinizde en son planlanan 1 saniyelik görevin ilk tamamlandığını ve tüm programın yaklaşık 3 saniyede (yani sadece en uzun görevin süresi kadar) bittiğini görüyorsunuz. Bu, işlerin arka planda gerçekten eş zamanlı yürütüldüğünün kanıtı.

Kodun Anatomisi

asyncio‘nun temelinde tek bir fikir var: bir görev beklemeye geçtiğinde (ağ yanıtı, dosya okuma, zamanlayıcı gibi), işlemciyi boşa harcamak yerine kontrolü merkezi bir olay döngüsüne (event loop) geri veriyorsunuz; döngü de o sırada bekleyen başka bir işi çalıştırıyor.

Bunu bir garson benzetmesiyle düşünebilirsiniz: Kötü bir garson bir masaya sipariş verir, mutfağın yemeği hazırlamasını orada dikilerek bekler, sonra ancak bir sonraki masaya geçer. İyi bir garson ise siparişi mutfağa iletir iletmez başka bir masaya döner; yemek hazır olduğunda tekrar o masaya uğrar. asyncio işte bu ikinci garson gibi çalışır — tek bir iş parçacığıyla (single-threaded) onlarca “masaya” (görev) aynı anda hizmet verebilir, çünkü hiçbir masada boşuna beklemez.

Bunu mümkün kılan üç yapı taşı var. İlki, async def ile tanımlanan eş yordam (coroutine) fonksiyonlar — bunlar normal fonksiyonlar gibi çalışmaz, çağrıldıklarında hemen çalışmaya başlamak yerine bir “awaitable” nesne döndürürler.

async def simulate_network_fetch(server_name: str, delay_seconds: int) -> dict:
    print(f"[Fetch {server_name}] Initiating connection...")

    # Engellemeyen (non-blocking) bekleme: event loop bu sırada
    # başka görevlere geçebilir
    await asyncio.sleep(delay_seconds)

    print(f"[Fetch {server_name}] Data packet downloaded successfully.")
    return {"server": server_name, "status": 200, "payload": "Success Data"}

Buradaki kritik ayrıntı time.sleep() yerine await asyncio.sleep() kullanılması. time.sleep() tüm programı dondurur (bloklar); await asyncio.sleep() ise yalnızca çalışan coroutine’i duraklatır ve event loop’un o süre boyunca başka görevlerle ilgilenmesine izin verir.

İkinci yapı taşı, görevleri event loop’a kaydeden asyncio.create_task(). Bir coroutine’i doğrudan await etmek onu hemen bekletir; create_task() ise görevi arka plana atar ve programın diğer satırlarına devam etmesini sağlar:

task1 = asyncio.create_task(simulate_network_fetch("API-Gateway-Europe", 3))
task2 = asyncio.create_task(simulate_network_fetch("Database-Cluster-US", 2))
task3 = asyncio.create_task(simulate_network_fetch("Cache-Node-Asia", 1))

# Üç görev de artık event loop kuyruğunda paralel biçimde ilerliyor
results = await asyncio.gather(task1, task2, task3)

Üçüncü parça ise asyncio.gather() — üç görevin de tamamlanmasını bekler ve sonuçlarını sıralı bir listede toplar. Burada kullandığımız gather fonksiyonuna alternatif olarak, Python’ın daha güncel sürümlerinde tanıtılan ve hata yönetimini kolaylaştıran TaskGroup yapısını da inceleyebilirsiniz; konuyu daha derinlemesine ele alan Python Asyncio ile Hızlanın: Modern Eşzamanlılık ve TaskGroup yazımıza göz atabilirsiniz.

Ortam Bilgisi
Yazar: Ahmet Aksoy · Tarih: 26.05.2026 · Python 3.12 · Ubuntu 24.04

Burada akla doğal bir soru gelebilir: Peki neden threading ya da multiprocessing kullanmıyoruz? Çünkü bu proje I/O-bound bir senaryo — yani zamanın çoğu ağ yanıtı beklemekle geçiyor, işlemci gerçekten çalışmıyor. Böyle durumlarda asyncio, thread açmanın getirdiği ek yükten (context switching, bellek) kaçınarak tek bir iş parçacığında binlerce bekleyen görevi yönetebilir. İşlemcinin gerçekten meşgul olduğu CPU-bound işler için ise durum farklı; bu konudaki performans karşılaştırmasını Python’da Hız Yarışı: Multiprocessing mi, Threading mi? yazımızda bulabilirsiniz.

Nasıl Çalıştırılır?

Projede standart kütüphane dışında hiçbir bağımlılık yok; sadece Python 3.12 veya üzeri bir sürüm gerekiyor.

python3 main.py

Çalıştırdığınızda, görevlerin planlanma sırasına değil bitiş sürelerine göre tamamlandığını gösteren bir çıktı alırsınız:

[Fetch Cache-Node-Asia] Data packet downloaded successfully.
[Fetch Database-Cluster-US] Data packet downloaded successfully.
[Fetch API-Gateway-Europe] Data packet downloaded successfully.

Total elapsed non-blocking pipeline duration: 3.0021 seconds.

Ne Öğrendik?

  • async def ile tanımlanan eş yordamların (coroutine) normal fonksiyonlardan farklı çalıştığı — çağrıldıklarında değil, await edildiklerinde yürütülüyorlar.
  • await asyncio.sleep() gibi engellemeyen (non-blocking) bekleme noktalarının, event loop’un kontrolü diğer görevlere devretmesine nasıl izin verdiği.
  • asyncio.create_task() ile bir görevi hemen event loop kuyruğuna kaydedip arka planda çalıştırmanın, asyncio.gather() ile de birden çok görevin sonucunu toplu biçimde beklemenin nasıl yapıldığı.
  • I/O-bound (ağ, dosya, veritabanı bekleme) ve CPU-bound (yoğun hesaplama) iş yükleri arasındaki farkın, hangi eşzamanlılık aracını (asyncio, threading, multiprocessing) seçeceğinizi nasıl belirlediği.
  • asyncio.create_task() ile oluşturulan görevlerin planlanma sırası ile tamamlanma sırasının birbirinden bağımsız olduğu — event loop, hangi görevin bekleme süresi bittiyse ona öncelik veriyor.

Kaynaklar ve Sonraki Adımlar

Bu temel örneği geliştirmek isterseniz gerçek bir HTTP isteği göndermek için aiohttp kütüphanesini deneyebilir, hata yönetimini try/except ile veya asyncio.gather(..., return_exceptions=True) parametresiyle güçlendirebilir, ya da yukarıda bahsettiğimiz TaskGroup yapısına geçerek daha modern bir hata yakalama modeli kurabilirsiniz. Konuyu iyice içselleştirdikten sonra, aynı senaryoyu threading ile yeniden yazıp iki yaklaşımın performansını kendi makinenizde karşılaştırmak da öğrenme sürecine oldukça değer katar.

Ahmet Aksoy

Not: Bu yazıda incelediğimiz kodu ve benzer projelerin kaynak kodlarını https://github.com/ahmetax/practical-python-examples adresinde bulabilirsiniz.

Bir Yanıt Bırak

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir