- Korutin,
awaitvə hadisə dövrü anlayışlarını izah etmək gather,create_taskvəTaskGroupilə korutinləri eyni vaxtda işlətməkwait_forilə vaxt limiti qoymaq vəasync forişlətmək- asyncio-nun nə vaxt fayda verdiyini və hadisə dövrünü nəyin blokladığını bilmək
Proqram internetdən 100 səhifə yükləməlidir. Hər sorğu təxminən 0,3 saniyəni sadəcə şəbəkəni gözləməyə sərf edir — bu müddətdə prosessor heç nə etmir. Ardıcıl işləsək, bu, 30 saniyəlik, əsasən boş gözləmədir. Çayxanadakı ofisiantı xatırla: o, bir sifariş hazır olana qədər mətbəxin qapısında dayanmır, bu arada növbəti masanın sifarişini götürür. Asinxron proqramlaşdırma bir axının məhz belə işləməsinə imkan verir: bir əməliyyat gözləyərkən digərləri irəliləyir.
Korutinlər, await və hadisə dövrü
async def ilə təyin olunan funksiya korutin funksiyasıdır. Onu çağırmaq gövdəni icra etmir — generator funksiyası kimi o da korutin obyekti qaytarır və bu obyekti await etmək lazımdır.
await korutini icra edir və nəticəsini gözləyir. Hələ bitməmiş əməliyyatın hər await-ində korutin dayanır və idarəni hadisə dövrünə (event loop) qaytarır — hansı dayanmış korutinin davam edəcəyinə qərar verən planlaşdırıcıya. Heç nə paralel işləmir: axın birdir, korutinlər isə növbəni könüllü olaraq bir-birinə verir. Buna kooperativ çoxməsələlilik deyilir.
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)▸ Gözlənilən nəticə
coroutine Hello, Aysel! AYSEL
await-i birbaşa ən üst səviyyədə yazmaq olar.Adi .py faylında funksiyadan kənar await sintaksis xətasıdır. Orada proqramın adətən main() adlanan bir giriş korutini olur və hadisə dövrü skriptin lap sonunda, bir dəfə **asyncio.run(main())** ilə işə salını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 ilə işə sal.gather ilə eyni vaxtda icra
asyncio.gather(*coroutines) bir neçə korutini birdən başladır və hamısı bitənə qədər gözləyir. Sətirlərin ardıcıllığına bax: hər üçü dərhal başlayır, yazılış sırasına görə yox, gözləmə müddətinə görə bitir. Nəticələr isə arqumentlərin sırası ilə qayıdır:
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)▸ Gözlənilən nəticə
start rates start weather start news done news done weather done rates ['rates (0.3 s)', 'weather (0.2 s)', 'news (0.1 s)']
İndi ölçək. Korutinləri bir-birinin ardınca gözləsək, gözləmələr toplanır; gather ilə isə onlar üst-üstə düşür, ona görə ümumi vaxt ən uzun gözləməyə bərabər 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')Tapşırıqlar, TaskGroup və vaxt limitləri
asyncio.create_task(coro) korutini dərhal arxa planda işləmək üçün planlaşdırır və tapşırıq (task) qaytarır. Bu arada başqa işlər görə, nəticəni isə sonra tapşırığı await edərək ala bilərsən. Python 3.11-dən bəri **asyncio.TaskGroup** strukturlaşdırılmış eyni vaxtda icra təklif edir: async with bloku bütün tapşırıqları bitənə qədər tamamlanmır, onlardan biri xəta versə, qalanları avtomatik ləğv olunur:
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()▸ Gözlənilən nəticə
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
Şəbəkə xidmətləri bəzən heç cavab vermir. asyncio.wait_for(coro, timeout) verilmiş saniyələrdən sonra əməliyyatı ləğv edir və TimeoutError qaldırır (Python 3.11-dən bəri asyncio.TimeoutError eyni sinifdir):
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')▸ Gözlənilən nəticə
the API did not answer in 0.2 s
Dilin başqa konstruksiyalarının da asinxron variantları var: hazırlanması və ya təmizlənməsi gözləmə tələb edən kontekst menecerləri (məsələn, şəbəkə əlaqəsi) üçün async with, asinxron iteratorlar üçün isə async for. Asinxron generator — içində yield olan async def funksiyasıdır:
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)▸ Gözlənilən nəticə
tick 1 tick 2 tick 3 [1, 4, 9, 16]
Tələlər və asyncio-nu nə vaxt seçməli
| Yanaşma | Nə üçün yaxşıdır | Nümunə |
|---|---|---|
asyncio | bir axında çoxlu eyni vaxtlı gözləmə (şəbəkə, soketlər) | çat serveri, veb-krauler, FastAPI |
axınlar (threading) | await edilə bilməyən bloklayan kitabxanalar | requests ilə bir neçə yükləmə |
proseslər (multiprocessing) | bütün prosessor nüvələrində ağır hesablamalar | şəkillərin emalı, simulyasiyalar |
Üç yeməyi asyncio.gather ilə eyni vaxtda bişir və nəticələr siyahısını çap et. Diqqət et: «ready» sətirləri bişmə müddətinə görə çıxır, siyahı isə menyu sırasını saxlayır.
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▸ Gözlənilən nəticə
ready: salad ready: soup ready: plov ['plov', 'salad', 'soup']
fetch_with_limit korutinini yaz: o, fetch-i limit saniyəlik vaxt limiti ilə işə salsın və nəticəni, vaxt bitəndə isə '<name>: timeout' sətrini qaytarsı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)▸ Gözlənilən nəticə
['fast: ok', 'slow: timeout']
Əsas fikirlər
async defkorutin təyin edir; onu çağırmaq korutin obyekti qaytarır,awaitisə onu icra edir.- Hər
await-də korutin dayanır və hadisə dövrü başqasını işə salır — bir axın, kooperativ çoxməsələlilik. - Skript
asyncio.run(main())ilə başlayır; brauzerdə və Jupyter-də birbaşaawaityazılır. gather,create_taskvəTaskGroupkorutinləri eyni vaxtda işlədir; ümumi vaxt ≈ ən uzun gözləmə.- Dövrü heç vaxt
time.sleepvə ya ağır hesablama ilə bloklama; asyncio gözləmə (I/O) tapşırıqları üçündür.
Özünü yoxla
10 sual. Hər düzgün cavab XP qazandırır.
async def load(): ... təyin olunub. load() çağırışı nə qaytarır?