Python-এ async/await: asyncio সময়সহ ব্যাখ্যা
async/await একটি thread-কে একসঙ্গে অনেক ধীর কাজের অপেক্ষায় রাখে। আসল সময়সহ gather, semaphore, timeout ও blocking call-এর ভুল দেখুন।

বেশিরভাগ প্রোগ্রাম সময় কাটায় অপেক্ষায়: web request, database, ফাইলের জন্য। async/await একটি thread-কে অপেক্ষা শুরু করে ইতিমধ্যে অন্য কাজে যেতে দেয়, ফলে অনেক অপেক্ষা একসঙ্গে মেলে। এটি CPU-ভারী code দ্রুত করে না, আর কয়েকটি core-এ সমান্তরালে code চালায় না। নিচের code ও সময় প্রকাশের আগে Python 3-এ চালানো (sleep network call-এর বদলে)।
মূল ধারণা
async defএকটি coroutine বানায়। এটি ডাকলে চলে না;awaitকরতে হয় বা event loop-কে দিতে হয়।awaitবলে "এটি শেষ না হওয়া পর্যন্ত আমাকে থামাও, অন্যদের চলতে দাও"।asyncio.run(main())event loop শুরু করে।asyncio.gather(...)কয়েকটি awaitable একসঙ্গে চালিয়ে ফল ক্রমে জড়ো করে।
পরপর বনাম একসঙ্গে, semaphore ও timeout
import asyncio, time
async def fetch(name, delay):
await asyncio.sleep(delay) # stands in for a network call
return f"{name} done"
async def main():
t = time.perf_counter()
for n in ("a", "b", "c"):
await fetch(n, 0.3) # one after another
print(f"one after another: {time.perf_counter() - t:.1f} s")
t = time.perf_counter()
results = await asyncio.gather(fetch("a", 0.3), fetch("b", 0.3), fetch("c", 0.3))
print(f"gather: {time.perf_counter() - t:.1f} s ->", results)
sem = asyncio.Semaphore(2) # at most two at once
active, peak = 0, 0
async def limited(i):
nonlocal active, peak
async with sem:
active += 1; peak = max(peak, active)
await asyncio.sleep(0.1)
active -= 1
await asyncio.gather(*(limited(i) for i in range(6)))
print("semaphore(2): at most", peak, "running at once")
try:
await asyncio.wait_for(fetch("slow", 1.0), timeout=0.2)
except asyncio.TimeoutError:
print("timeout: gave up after 0.2 s")
asyncio.run(main())
one after another: 0.9 s
gather: 0.3 s -> ['a done', 'b done', 'c done']
semaphore(2): at most 2 running at once
timeout: gave up after 0.2 s
0.3 সেকেন্ডের তিনটি অপেক্ষা পরপর 0.9 সেকেন্ড আর gather-এ 0.3 সেকেন্ড নিয়েছে। semaphore একসঙ্গে চলা কাজ ২টিতে ধরে রেখেছে, এভাবে শতশত request দিয়ে একটি সেবার ওপর চাপ এড়ানো যায়। wait_for ১ সেকেন্ড লাগা কাজের জন্য ০.২ সেকেন্ড পর অপেক্ষা ছেড়েছে।
যে ভুলে সব জমে যায়
সবাই একটি thread ভাগ করে, তাই একটি blocking call সব কাজ থামায়।
async def slow_block():
time.sleep(0.5) # a blocking call inside async code freezes everything
ticks = []
async def ticker():
for _ in range(3):
ticks.append(round(time.perf_counter() - t0, 1)); await asyncio.sleep(0.1)
t0 = time.perf_counter()
await asyncio.gather(ticker(), slow_block())
print("ticker ticks while blocked by time.sleep:", ticks)
ticker ticks while blocked by time.sleep: [0.0, 0.5, 0.6]
ticker-এর প্রতি 0.1 সেকেন্ডে টিক করার কথা। তার বদলে 0.0-তে টিক করে 0.5 পর্যন্ত অপেক্ষা করেছে, কারণ time.sleep(0.5) loop আটকে দিয়েছে। এর বদলে await asyncio.sleep ব্যবহার করুন, বা blocking কাজ asyncio.to_thread(...) দিয়ে thread-এ সরান।
কখন asyncio
- ভালো মানায়: অনেক সমসাময়িক network call, web server, chat, ভদ্রভাবে অনেক পেজ scrape, যা মূলত অপেক্ষা করে।
- কম মানায়: ভারী গণনা (process ব্যবহার করুন), এক-দুটি call-এর সহজ script, blocking library-নির্ভর code।
সাধারণ ভুল
awaitছাড়া coroutine ডাকা, যা শুধু একটি object বানায় ও কিছুই চালায় না (Python সতর্ক করে "coroutine was never awaited")।- async code-এ blocking library (
requests,time.sleep) ব্যবহার। - সীমা ছাড়াই হাজার হাজার task একসঙ্গে শুরু। semaphore ব্যবহার করুন।
- একটি ব্যর্থ task অন্যদের লুকিয়ে ফেলা।
gather(..., return_exceptions=True)ত্রুটিকে ফল হিসেবে ফেরায়। - timeout ভুলে যাওয়া। timeout ছাড়া request চিরকাল অপেক্ষা করতে পারে।
মূল কথা: যে code অপেক্ষা করে তাতে async/await ব্যবহার করুন, স্বাধীন অপেক্ষা gather দিয়ে চালান, semaphore দিয়ে সমসাময়িকতা সীমিত করুন, timeout দিন, আর loop কখনও আটকাবেন না।
Programming roadmaps সিরিজ, পর্ব ৩ (ভালো Python লেখা): 3 নম্বর (মোট 5)। আগের লেখা: Python-এ regular expression: ফাঁদসহ ব্যবহারিক গাইড.


