- Eşyordam,
awaitve olay döngüsü kavramlarını açıklamak gather,create_taskveTaskGroupile eşyordamları eşzamanlı çalıştırmakwait_forile zaman aşımı koymak veasync forkullanmak- asyncio'nun ne zaman işe yaradığını ve olay döngüsünü neyin engellediğini bilmek
Bir programın internetten 100 sayfa indirmesi gerekiyor. Her istek yaklaşık 0,3 saniyesini yalnızca ağı beklemekle geçirir; bu sürede işlemci hiçbir şey yapmaz. Sırayla yapılırsa bu, çoğu boş geçen 30 saniyelik bir beklemedir. Bir çay bahçesindeki garsonu düşün: bir sipariş hazırlanana kadar mutfak kapısında dikilmez, bu arada sonraki masanın siparişini alır. Asenkron programlama, tek bir iş parçacığının da böyle çalışmasını sağlar: bir işlem beklerken diğerleri ilerler.
Eşyordamlar, await ve olay döngüsü
async def ile tanımlanan fonksiyon bir eşyordam fonksiyonudur. Onu çağırmak gövdesini çalıştırmaz; tıpkı bir üreteç fonksiyonu gibi bir eşyordam nesnesi döndürür ve bu nesnenin await ile beklenmesi gerekir.
await bir eşyordamı çalıştırır ve sonucunu bekler. Henüz bitmemiş bir işlemdeki her await'te eşyordam duraklar ve denetimi olay döngüsüne (event loop) geri verir; olay döngüsü, duraklamış eşyordamlardan hangisinin devam edeceğine karar veren zamanlayıcıdır. Hiçbir şey paralel çalışmaz: tek bir iş parçacığı vardır ve eşyordamlar sırayı gönüllü olarak birbirine bırakır. Buna işbirlikçi çoklu görev (cooperative multitasking) denir.
import asyncio
async def greet(name, delay):
await asyncio.sleep(delay)
print(f'Hello, {name}!')
return name.upper()
coro = greet('Aysel', 0.1)
print(type(coro).__name__)
result = await coro
print(result)▸ Beklenen çıktı
coroutine Hello, Aysel! AYSEL
await doğrudan en üst düzeyde yazılabilir.Sıradan bir .py dosyasında fonksiyon dışındaki await bir sözdizimi hatasıdır. Orada programın genellikle main() adlı tek bir giriş eşyordamı olur ve olay döngüsü betiğin en sonunda bir kez **asyncio.run(main())** ile başlatılır:
import asyncio
async def fetch(name, seconds):
await asyncio.sleep(seconds)
return f'{name} ({seconds} s)'
async def main():
results = await asyncio.gather(fetch('rates', 0.3), fetch('news', 0.1))
print(results)
if __name__ == '__main__':
asyncio.run(main())['rates (0.3 s)', 'news (0.1 s)']
python app.py ile çalıştır.gather ile eşzamanlı çalıştırma
asyncio.gather(*coroutines) birkaç eşyordamı aynı anda başlatır ve hepsi bitene kadar bekler. Satırların sırasına bak: üçü de hemen başlar ve yazılış sırasına göre değil, bekleme sürelerine göre biter. Sonuçlar ise argümanların sırasıyla geri döner:
import asyncio
async def fetch(name, seconds):
print(f'start {name}')
await asyncio.sleep(seconds)
print(f'done {name}')
return f'{name} ({seconds} s)'
results = await asyncio.gather(
fetch('rates', 0.3),
fetch('weather', 0.2),
fetch('news', 0.1),
)
print(results)▸ Beklenen çıktı
start rates start weather start news done news done weather done rates ['rates (0.3 s)', 'weather (0.2 s)', 'news (0.1 s)']
Şimdi ölçelim. Eşyordamlar birbiri ardına beklenirse beklemeler toplanır; gather ile ise üst üste biner, bu yüzden toplam süre en uzun beklemeye eşit olur:
import asyncio
import time
async def fetch(seconds):
await asyncio.sleep(seconds)
start = time.perf_counter()
for s in (0.3, 0.2, 0.1):
await fetch(s)
print(f'one after another: {time.perf_counter() - start:.2f} s')
start = time.perf_counter()
await asyncio.gather(fetch(0.3), fetch(0.2), fetch(0.1))
print(f'with gather: {time.perf_counter() - start:.2f} s')Görevler, TaskGroup ve zaman aşımları
asyncio.create_task(coro), bir eşyordamı hemen arka planda çalışmak üzere zamanlar ve bir görev (task) döndürür. Bu arada başka işler yapabilir, sonucu ise daha sonra görevi await ederek alabilirsin. Python 3.11'den beri **asyncio.TaskGroup** yapılandırılmış eşzamanlılık sunar: async with bloğu tüm görevleri bitmeden sona ermez ve biri hata verirse diğerleri otomatik olarak iptal edilir:
import asyncio
async def worker(name, seconds):
await asyncio.sleep(seconds)
print(f'{name} finished')
return seconds
async def main():
task = asyncio.create_task(worker('backup', 0.2))
print('task created, doing other work...')
await asyncio.sleep(0.1)
print('other work done, task done?', task.done())
print('result:', await task)
async with asyncio.TaskGroup() as tg:
t1 = tg.create_task(worker('A', 0.2))
t2 = tg.create_task(worker('B', 0.1))
print('group results:', t1.result(), t2.result())
await main()▸ Beklenen çıktı
task created, doing other work... other work done, task done? False backup finished result: 0.2 B finished A finished group results: 0.2 0.1
Ağ hizmetleri bazen hiç yanıt vermez. asyncio.wait_for(coro, timeout), verilen saniye sonunda işlemi iptal eder ve TimeoutError fırlatır (Python 3.11'den beri asyncio.TimeoutError aynı sınıftır):
import asyncio
async def slow_api():
await asyncio.sleep(1)
return 'data'
try:
result = await asyncio.wait_for(slow_api(), timeout=0.2)
except TimeoutError:
print('the API did not answer in 0.2 s')▸ Beklenen çıktı
the API did not answer in 0.2 s
Dilin başka yapılarının da asenkron sürümleri vardır: hazırlığı ya da temizliği bekleme gerektiren bağlam yöneticileri (örneğin bir ağ bağlantısı) için async with, asenkron yineleyiciler için ise async for. Asenkron üreteç, içinde yield bulunan bir async def fonksiyonudur:
import asyncio
async def ticker(n, delay):
for i in range(1, n + 1):
await asyncio.sleep(delay)
yield i
async for value in ticker(3, 0.05):
print('tick', value)
squares = [v * v async for v in ticker(4, 0.01)]
print(squares)▸ Beklenen çıktı
tick 1 tick 2 tick 3 [1, 4, 9, 16]
Tuzaklar ve asyncio'yu ne zaman seçmeli
| Yaklaşım | Ne için iyidir | Örnek |
|---|---|---|
asyncio | tek iş parçacığında çok sayıda eşzamanlı bekleme (ağ, soketler) | sohbet sunucusu, web tarayıcı robotu, FastAPI |
iş parçacıkları (threading) | await edilemeyen engelleyici kütüphaneler | requests ile birkaç indirme |
süreçler (multiprocessing) | tüm işlemci çekirdeklerinde ağır hesaplamalar | görüntü işleme, simülasyonlar |
Üç yemeği asyncio.gather ile aynı anda pişir ve sonuç listesini yazdır. Dikkat: “ready” satırları pişme süresine göre çıkar, liste ise menü sırasını korur.
import asyncio
async def cook(dish, seconds):
await asyncio.sleep(seconds)
print('ready:', dish)
return dish
menu = [('plov', 0.3), ('salad', 0.1), ('soup', 0.2)]
# cook all dishes at the same time with asyncio.gather and print the results▸ Beklenen çıktı
ready: salad ready: soup ready: plov ['plov', 'salad', 'soup']
fetch_with_limit eşyordamını yaz: fetch'i limit saniyelik bir süre sınırıyla çalıştırsın ve sonucunu, süre dolarsa da '<name>: timeout' dizesini döndürsün.
import asyncio
async def fetch(name, seconds):
await asyncio.sleep(seconds)
return f'{name}: ok'
async def fetch_with_limit(name, seconds, limit):
# return fetch's result, or f'{name}: timeout' if it takes longer than limit
...
results = await asyncio.gather(
fetch_with_limit('fast', 0.05, 0.2),
fetch_with_limit('slow', 0.5, 0.2),
)
print(results)▸ Beklenen çıktı
['fast: ok', 'slow: timeout']
Önemli noktalar
async defbir eşyordam tanımlar; onu çağırmak bir eşyordam nesnesi döndürür,awaitise onu çalıştırır.- Her
await'te eşyordam duraklar ve olay döngüsü bir başkasını çalıştırır: tek iş parçacığı, işbirlikçi çoklu görev. - Bir betik
asyncio.run(main())ile başlar; tarayıcıda ve Jupyter'de doğrudanawaityazılır. gather,create_taskveTaskGroupeşyordamları eşzamanlı çalıştırır; toplam süre ≈ en uzun bekleme.- Döngüyü
time.sleepya da ağır hesaplamalarla engelleme; asyncio bekleme (G/Ç) görevleri içindir.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.
async def load(): ... tanımlı. load() çağrısı ne döndürür?