Python Advance 📂 Async Await · 1 of 1 348 min read

Python Async Await Tutorial with Diagrams & Real Examples (2026)

Master Python's async / await from first principles. Learn what coroutines really are, how the event loop schedules them, and why the same one-thread program can be 10× faster at network work. Includes side-by-side sync vs concurrent benchmarks, a real weather-aggregator example with aiohttp, timeout & cancellation patterns, TaskGroup, semaphore-bounded concurrency, and eight golden rules

🌐
SectionSection第 01 节

The Story That Explains async / await ☕async / await समझाने वाली कहानी ☕一个讲清 async / await 的故事 ☕

The Coffee Shop BaristaCoffee Shop का Barista咖啡店的咖啡师
Imagine a barista who handles one order at a time. A customer orders a latte. The barista grinds the beans, pulls the espresso shot (30 seconds), steams the milk (40 seconds), pours, hands it over — and only then looks at the next customer. Ten people wait while the barista stands and watches the machine.

Now imagine a smarter barista. Order taken → espresso started → while it runs, take the next order, start another shot, steam milk for order #1, check order #2's shot. The barista never stands idle. Same one person, same two hands — but many more drinks per hour, because they never wait on the machine.

That second barista is async / await. Python gets no extra threads or cores — it simply stops standing around while it waits for slow things (network, disk, timers).
एक barista सोचिए जो एक समय में एक order संभालता है। Customer latte माँगता है। Barista beans पीसता है, espresso shot निकालता है (30 seconds), दूध गर्म करता है (40 seconds), डालता है, देता है — और तभी अगले customer को देखता है। दस लोग इंतज़ार करते हैं जबकि barista खड़ा होकर machine देखता रहता है।

अब एक समझदार barista सोचिए। Order लिया → espresso शुरू → जब वह चल रहा है, अगला order लिया, दूसरा shot शुरू किया, order #1 का दूध गर्म किया, order #2 का shot देखा। Barista कभी ख़ाली नहीं खड़ा। वही एक इंसान, वही दो हाथ — पर हर घंटे कहीं ज़्यादा drinks, क्योंकि वह कभी machine का इंतज़ार नहीं करता।

वह दूसरा barista async / await है। Python को कोई extra threads या cores नहीं मिलते — वह बस धीमी चीज़ों (network, disk, timers) का इंतज़ार करते हुए खड़ा रहना बंद कर देता है।
想象一位一次只处理一个订单的咖啡师。顾客点了一杯拿铁。咖啡师磨豆、萃取浓缩(30 秒)、打奶泡(40 秒)、倒好、递出 — 然后才看下一位顾客。十个人在排队,而咖啡师只是站着盯着咖啡机。

再想象一位更聪明的咖啡师。接单 → 开始萃取 → 趁机器运转时,接下一单,开始另一杯萃取,给 1 号订单打奶泡,看看 2 号订单的萃取。咖啡师从不闲着。还是同一个人、同一双手 — 但每小时能做出多得多的饮品,因为他从不干等机器。

第二位咖啡师就是 async / await。Python 并没有多出线程或核心 — 它只是不再在等待慢事情(网络、磁盘、定时器)时干站着。

async / await is Python's way to write code that pauses cleanly at slow I/O and lets other work run during the wait. It runs on one thread, it is cooperative, and it is much faster for programs that spend most of their time waiting on the network, a database, files or timers.

async / await Python का ऐसा code लिखने का तरीक़ा है जो धीमे I/O पर साफ़ रुकता है और इंतज़ार के दौरान दूसरा काम चलने देता है। यह एक thread पर चलता है, cooperative है, और उन programs के लिए बहुत तेज़ है जो ज़्यादातर समय network, database, files या timers का इंतज़ार करते हैं।

async / await 是 Python 编写这样一种代码的方式:它在缓慢的 I/O 处干净地暂停,并在等待期间让其他工作运行。它运行在 一个线程 上,是 协作式 的,对于大部分时间都在等待网络、数据库、文件或定时器的程序来说要快得多。

🧠
The Core Insightमूल बात核心要点

async / await does not make CPU work faster. It makes waiting cheaper — by doing something else during the wait. Use it when your program spends its time waiting on outside things, not when it spends its time computing.

async / await CPU काम को तेज़ नहीं करता। यह इंतज़ार को सस्ता बनाता है — इंतज़ार के दौरान कुछ और करके। इसे तब लीजिए जब आपका program बाहरी चीज़ों का इंतज़ार करता है, तब नहीं जब वह गणना करता है।

async / await 不会 让 CPU 工作变快。它让 等待 变得更划算 — 在等待期间去做别的事。当程序的时间花在等待外部事物上时使用它,而不是花在计算上时。

▶️
Nine Simulators and a Quizनौ Simulators और एक Quiz九个模拟器和一个小测验

Look for the interactive boxes: the Await Patterns Lab (Section 03), the Event Loop Stepper (04), the Sync vs Async Race (05), the Blocking Lab (09), the Threads vs asyncio at Scale and Lost Update Lab (10), the Semaphore Lab (12), the Failure Lab (14) and the Bug Doctor (15). Their data comes from real Python runs.

Interactive डिब्बे खोजें: Await Patterns Lab (Section 03), Event Loop Stepper (04), Sync बनाम Async Race (05), Blocking Lab (09), बड़े पैमाने पर Threads बनाम asyncio और Lost Update Lab (10), Semaphore Lab (12), Failure Lab (14) और Bug Doctor (15)। इनका data असली Python runs से है।

请留意这些互动框:await 模式实验室(第 03 节)、事件循环单步演示(04)、同步 vs 异步赛跑(05)、阻塞实验室(09)、大规模下的线程 vs asyncio 和 丢失更新实验室(10)、信号量实验室(12)、失败实验室(14)和 Bug 医生(15)。它们的数据来自真实的 Python 运行。

ℹ️
About the Network ExamplesNetwork Examples के बारे में关于网络示例

Our test machine has no internet. So the HTTP examples talk to a small local test server on 127.0.0.1:8765 that copies the endpoints the original used (/delay/1, /anything/i, and a weather endpoint shaped like Open-Meteo, returning fixed test values). The client code is real aiohttp and the timings are real. In your own code, use the real URLs.

हमारी test machine में internet नहीं है। इसलिए HTTP examples 127.0.0.1:8765 पर एक छोटे local test server से बात करते हैं जो मूल वाले endpoints की नक़ल करता है (/delay/1, /anything/i, और Open-Meteo जैसा एक weather endpoint, जो तय test values लौटाता है)। Client code असली aiohttp है और timings असली हैं। अपने code में असली URLs लीजिए।

我们的测试机器没有互联网。所以 HTTP 示例连接的是 127.0.0.1:8765 上的一个小型 本地测试服务器,它模仿了原文使用的接口(/delay/1、/anything/i,以及一个仿照 Open-Meteo 格式、返回固定测试值的天气接口)。客户端代码是真正的 aiohttp,计时也是真实的。在你自己的代码中,请使用真实的 URL。


SectionSection第 02 节

The Problem — Why Sync Code Is Slow at I/O 🐢समस्या — Sync Code I/O में धीमा क्यों है 🐢问题 — 为什么同步代码在 I/O 上很慢 🐢

Fetch three URLs, each taking about 1 second because the network is slow. Synchronous code stops the whole program during every wait.

तीन URLs fetch कीजिए, हर एक लगभग 1 second लेता है क्योंकि network धीमा है। Synchronous code हर इंतज़ार के दौरान पूरा program रोक देता है।

获取三个 URL,因为网络慢,每个大约要 1 秒。同步代码在每次等待时都会让整个程序停下来。

⏳ Synchronous (blocking)Synchronous (blocking)同步(阻塞)
Stepक़दम步骤Timeसमय时间
Fetch URL 1URL 1 fetch获取 URL 10.0 s → 1.0 s
Fetch URL 2URL 2 fetch获取 URL 21.0 s → 2.0 s
Fetch URL 3URL 3 fetch获取 URL 32.0 s → 3.0 s
Totalकुल总计3.0 s
⚡ Asynchronous (concurrent)Asynchronous (concurrent)异步(并发)
Stepक़दम步骤Timeसमय时间
Start URL 1URL 1 शुरू启动 URL 10.0 s (returns at once)0.0 s (तुरंत लौटता है)0.0 秒(立即返回)
Start URL 2URL 2 शुरू启动 URL 20.0 s (returns at once)0.0 s (तुरंत लौटता है)0.0 秒(立即返回)
Start URL 3URL 3 शुरू启动 URL 30.0 s (returns at once)0.0 s (तुरंत लौटता है)0.0 秒(立即返回)
All finishedसब ख़त्म全部完成~1.0 s
Figure 1 — Sync vs Async Timeline (3 calls, 1 s each)चित्र 1 — Sync बनाम Async Timeline (3 calls, हर एक 1 s)图 1 — 同步与异步时间线(3 次调用,各 1 秒)
Sync vs async timelineSYNC — one after anotherSYNC — एक के बाद एक同步 — 一个接一个Awaiting 1 sBwaiting 1 sCwaiting 1 s3 sASYNC — all waits overlapASYNC — सारे इंतज़ार साथ-साथ异步 — 所有等待重叠Awaiting 1 sBwaiting 1 sCwaiting 1 s← 1 s

Async starts all three, and the waits overlap: about 1 s instead of 3 s.

Async तीनों शुरू करता है, और इंतज़ार साथ-साथ होते हैं: 3 s की जगह लगभग 1 s।

异步同时启动三个,等待相互重叠:约 1 秒而不是 3 秒。

⚠️
The Waste of Blocking I/OBlocking I/O की बर्बादी阻塞 I/O 的浪费

In the synchronous version the CPU sits almost idle for 3 seconds while it waits for the network. Async uses that time. For I/O-heavy programs (web scrapers, APIs, chat bots, database workers) the speed-up is often 10× or more — it grows with the number of waits that can overlap.

Synchronous version में CPU network का इंतज़ार करते हुए 3 seconds लगभग ख़ाली बैठा रहता है। Async वह समय इस्तेमाल करता है। I/O-भारी programs (web scrapers, APIs, chat bots, database workers) में speed-up अक्सर 10× या ज़्यादा होता है — यह साथ-साथ हो सकने वाले इंतज़ारों की संख्या के साथ बढ़ता है।

在同步版本中,CPU 在等待网络的 3 秒里几乎空闲。异步把这段时间利用起来。对于 I/O 密集的程序(网络爬虫、API、聊天机器人、数据库工作者),加速常常达到 10 倍以上 — 能重叠的等待越多,加速越大。


SectionSection第 03 节

The Three Building Blocks 🧱तीन बुनियादी हिस्से 🧱三个基本组成部分 🧱

🔁
1 — Coroutine1 — Coroutine1 — 协程
async def
A function defined with async def. Calling it does not run it — it returns a coroutine object, a paused, resumable computation. Nothing happens until the event loop runs it.
async def से बना function। इसे बुलाने से यह चलता नहीं — यह एक coroutine object लौटाता है, एक रुकी हुई, फिर शुरू हो सकने वाली गणना। Event loop के चलाने तक कुछ नहीं होता।
用 async def 定义的函数。调用它 不会 运行它 — 它返回一个 协程对象,一个暂停的、可恢复的计算。在事件循环运行它之前,什么都不会发生。
⏳
2 — await2 — await2 — await
pause here, let others runयहाँ रुको, दूसरों को चलने दो在此暂停,让别人运行
Inside a coroutine, await x means: "pause me and give control back to the event loop until x is done." Only allowed inside async def.
Coroutine के अंदर await x का मतलब है: "मुझे रोको और x पूरा होने तक control event loop को वापस दो।" सिर्फ़ async def के अंदर चलता है।
在协程内部,await x 的意思是:"暂停我,把控制权交还给事件循环,直到 x 完成。"只能在 async def 中使用。
🔄
3 — Event Loop3 — Event Loop3 — 事件循环
asyncio.run(...)
The scheduler that runs coroutines, tracks which ones are waiting, and resumes them when their data or timer is ready. You start it with asyncio.run().
वह scheduler जो coroutines चलाता है, देखता है कौन-से इंतज़ार में हैं, और उनका data या timer तैयार होने पर उन्हें फिर चलाता है। इसे asyncio.run() से शुरू करते हैं।
负责运行协程的调度器,它记录哪些协程在等待,并在它们的数据或定时器就绪时恢复它们。用 asyncio.run() 启动它。

Your first async functionआपका पहला async function你的第一个异步函数

import asyncio, time

async def greet(name):
    print(f"Hello {name}, starting...")
    await asyncio.sleep(1)          # pause 1 s WITHOUT blocking
    print(f"Goodbye {name}")

coro = greet("Mohit")               # calling greet() only CREATES a coroutine
print(type(coro).__name__, "- nothing has run yet")

t0 = time.perf_counter()
asyncio.run(coro)                   # asyncio.run() starts the event loop and runs it
print(f"took {time.perf_counter() - t0:.2f}s")
OUTPUT
coroutine - nothing has run yet Hello Mohit, starting... Goodbye Mohit took 1.00s

We added two lines to the original to make the key point visible: greet("Mohit") alone printed nothing — it only made a coroutine object. The greeting ran when asyncio.run() started the loop.

मुख्य बात दिखाने के लिए हमने मूल में दो lines जोड़ीं: अकेले greet("Mohit") ने कुछ print नहीं किया — इसने बस एक coroutine object बनाया। Greeting तब चली जब asyncio.run() ने loop शुरू किया।

为了让关键点更直观,我们在原文基础上加了两行:单独调用 greet("Mohit") 什么也没打印 — 它只创建了一个 coroutine 对象。直到 asyncio.run() 启动事件循环,问候才真正运行。

Figure 2 — Coroutine State Machineचित्र 2 — Coroutine State Machine图 2 — 协程状态机
Coroutine state machineCREATEDgreet("Mohit")RUNNINGon the loopSUSPENDEDat awaitDONEreturns valueawaitresumea coroutine can go RUNNING ⇄ SUSPENDED many timescoroutine कई बार RUNNING ⇄ SUSPENDED हो सकता है协程可以多次在 运行 ⇄ 挂起 之间切换

Each await suspends the coroutine; the loop resumes it later.

हर await coroutine को रोकता है; loop बाद में उसे फिर चलाता है।

每个 await 都会挂起协程,之后由事件循环恢复。

💡
time.sleep() vs asyncio.sleep()time.sleep() बनाम asyncio.sleep()time.sleep() 与 asyncio.sleep()

time.sleep(1) blocks the whole thread — in async code nothing else can run. await asyncio.sleep(1) waits for 1 second but lets other coroutines run during the pause. Always use the async version inside async code. Section 09 shows the difference with a real run.

time.sleep(1) पूरे thread को रोक देता है — async code में और कुछ नहीं चल सकता। await asyncio.sleep(1) 1 second रुकता है पर उस दौरान दूसरे coroutines को चलने देता है। Async code में हमेशा async version लीजिए। Section 09 असली run से फ़र्क़ दिखाता है।

time.sleep(1) 会阻塞整个线程 — 在异步代码中其他任何东西都无法运行。await asyncio.sleep(1) 等待 1 秒,但在暂停期间让其他协程运行。在异步代码中始终使用异步版本。第 09 节用真实运行展示了区别。

⏳ await in depth — what really happens⏳ await गहराई से — असल में क्या होता है⏳ 深入理解 await — 到底发生了什么

await is the heart of async code, so let us look at it slowly. x = await something means: "pause this coroutine until something is finished, then give me its result." While this coroutine is paused, the event loop is free to run other tasks. Four facts follow from this.

await async code का दिल है, तो इसे धीरे-धीरे देखते हैं। x = await something का मतलब है: "इस coroutine को तब तक रोको जब तक something पूरा न हो, फिर मुझे उसका result दो।" जब यह coroutine रुका होता है, event loop दूसरे tasks चलाने के लिए आज़ाद होता है। इससे चार बातें निकलती हैं।

await 是异步代码的核心,我们来慢慢看。x = await something 的意思是:"暂停这个协程,直到 something 完成,然后把它的结果给我。"在这个协程暂停期间,事件循环可以自由地运行其他任务。由此可以得出四个事实。

Figure 2b — What Happens at One awaitचित्र 2b — एक await पर क्या होता है图 2b — 一次 await 时发生了什么
What happens at one awaitcoroutine fetch()coroutine fetch()协程 fetch()event loopevent loop事件循环other tasksदूसरे tasks其他任务runs...① await sleep(1)timer② park fetch,set a timer③ other tasks runSUSPENDED — not using the CPU④ timer done⑤ resumesOnly the coroutine that awaits is paused — the program keeps running.सिर्फ़ await करने वाला coroutine रुकता है — program चलता रहता है।只有执行 await 的协程被暂停 — 程序本身仍在运行。

Pause, let others run, resume with the result.

रुको, दूसरों को चलने दो, result के साथ फिर चलो।

暂停,让别人运行,带着结果恢复。

1️⃣
await waits — it does not start things in the backgroundawait इंतज़ार करता है — पीछे कुछ शुरू नहीं करताawait 是等待 — 不会在后台启动任务
awaits run one after anotherawaits एक के बाद एकawait 是依次进行的
Two await lines in a row run one after the other. To overlap waits, create tasks first (create_task, gather) and await them afterwards.
लगातार दो await lines एक के बाद एक चलती हैं। इंतज़ार साथ-साथ करने हों तो पहले tasks बनाइए (create_task, gather) और बाद में उन्हें await कीजिए।
连续两行 await 会依次执行。想让等待重叠,就先创建任务(create_task、gather),之后再 await 它们。
2️⃣
await hands back the result — or the errorawait result लौटाता है — या errorawait 交回结果 — 或者错误
like a normal function callसामान्य function call जैसा就像普通函数调用
x = await f() gives you what f returned. If f raised an exception, it comes out at the await line, so normal try/except works.
x = await f() आपको वह देता है जो f ने लौटाया। अगर f ने exception दिया, तो वह await वाली line पर निकलता है, इसलिए सामान्य try/except चलता है।
x = await f() 会给你 f 的返回值。如果 f 抛出异常,异常会在 await 这一行冒出来,所以普通的 try/except 就能用。
3️⃣
only a real wait gives others a turnसिर्फ़ असली इंतज़ार दूसरों को मौका देता है只有真正的等待才会让出机会
await is a possible switch pointawait एक संभावित switch point हैawait 是可能的切换点
If the awaited coroutine never waits for anything (no sleep, no I/O), await runs straight through and no other task runs. await asyncio.sleep(0) is the explicit "let others run now".
अगर await किया गया coroutine कभी किसी चीज़ का इंतज़ार नहीं करता (न sleep, न I/O), तो await सीधे चल जाता है और कोई दूसरा task नहीं चलता। await asyncio.sleep(0) साफ़-साफ़ "अब दूसरों को चलने दो" है।
如果被 await 的协程从不等待任何东西(没有 sleep,没有 I/O),await 会一路执行下去,其他任务没有机会运行。await asyncio.sleep(0) 就是明确的"现在让别人运行"。

Fact 1 — await waits; create_task overlapsबात 1 — await इंतज़ार करता है; create_task साथ चलाता है事实 1 — await 是等待;create_task 才能重叠

import asyncio, time

async def job(name, secs):
    print(f"{time.perf_counter() - T0:4.1f}s  {name} starts")
    await asyncio.sleep(secs)
    print(f"{time.perf_counter() - T0:4.1f}s  {name} done")
    return name.upper()

async def main():
    a = await job("a", 1)      # main pauses here until job a is DONE
    b = await job("b", 1)      # only then does job b even start
    print("results:", a, b)

T0 = time.perf_counter()
asyncio.run(main())
print(f"total {time.perf_counter() - T0:.1f}s")
OUTPUT
0.0s a starts 1.0s a done 1.0s b starts 2.0s b done results: A B total 2.0s

Job b did not even start until job a was done: 2 seconds in total. Now the same jobs, scheduled as tasks first:

Job a पूरा होने तक job b शुरू भी नहीं हुआ: कुल 2 सेकंड। अब वही jobs, पहले tasks बनाकर:

直到任务 a 完成,任务 b 才 开始:总共 2 秒。现在把同样的任务先安排成 Task:

import asyncio, time

async def job(name, secs):
    print(f"{time.perf_counter() - T0:4.1f}s  {name} starts")
    await asyncio.sleep(secs)
    print(f"{time.perf_counter() - T0:4.1f}s  {name} done")
    return name.upper()

async def main():
    ta = asyncio.create_task(job("a", 1))   # scheduled now
    tb = asyncio.create_task(job("b", 1))   # scheduled now
    a = await ta               # main waits; meanwhile BOTH jobs run
    b = await tb               # already finished - returns at once
    print("results:", a, b)

T0 = time.perf_counter()
asyncio.run(main())
print(f"total {time.perf_counter() - T0:.1f}s")
OUTPUT
0.0s a starts 0.0s b starts 1.0s a done 1.0s b done results: A B total 1.0s

Fact 2 — await returns the value and passes on errorsबात 2 — await value लौटाता है और errors आगे देता है事实 2 — await 返回值并传递错误

import asyncio

async def fetch_price(symbol):
    await asyncio.sleep(0.1)
    if symbol == "XYZ":
        raise ValueError(f"unknown symbol {symbol}")
    return 142.35

async def main():
    price = await fetch_price("AAPL")        # await gives back the return value
    print("price:", price)
    try:
        await fetch_price("XYZ")             # an error inside comes out HERE
    except ValueError as e:
        print("caught at the await line:", e)

asyncio.run(main())
OUTPUT
price: 142.35 caught at the await line: unknown symbol XYZ

Fact 3 — an await only gives others a turn if it really waitsबात 3 — await दूसरों को मौका तभी देता है जब सच में इंतज़ार करे事实 3 — 只有真正等待时,await 才会让出机会

import asyncio

async def quick():
    return 1                    # awaits nothing - never pauses

async def ticker():
    for i in range(3):
        print("  tick", i)
        await asyncio.sleep(0)  # a real pause point

async def worker(really_pause):
    for i in range(3):
        if really_pause:
            await asyncio.sleep(0)   # gives the loop a turn
        else:
            await quick()            # runs straight through, no turn given
        print("work", i)

async def main(really_pause):
    print(f"--- really_pause={really_pause}")
    await asyncio.gather(worker(really_pause), ticker())

asyncio.run(main(False))
asyncio.run(main(True))
OUTPUT
--- really_pause=False work 0 work 1 work 2 tick 0 tick 1 tick 2 --- really_pause=True tick 0 work 0 tick 1 work 1 tick 2 work 2

With await quick(), the worker printed all three lines before the ticker got a single turn — quick() never paused. With await asyncio.sleep(0) they took turns.

await quick() के साथ worker ने तीनों lines print कर दीं, ticker को एक भी मौका मिलने से पहले — quick() कभी रुका ही नहीं। await asyncio.sleep(0) के साथ दोनों ने बारी-बारी से चलाया।

用 await quick() 时,worker 把三行都打印完,ticker 才第一次轮到 — quick() 从未暂停。用 await asyncio.sleep(0) 时,二者轮流运行。

What can you await?आप किसे await कर सकते हैं?哪些东西可以 await?

AwaitableAwaitable可等待对象Where it comes fromकहाँ से आता है从哪里来Noteटिप्पणी说明
CoroutineCoroutine协程calling an async def functionasync def function को बुलाना调用 async def 函数runs inside the current task, step by stepमौजूदा task के अंदर, कदम-दर-कदम चलता है在当前任务内部逐步运行
TaskTask任务asyncio.create_task(coro)already scheduled — it runs even before you await itपहले से scheduled — await करने से पहले भी चलता है已被调度 — 在你 await 之前就已在运行
FutureFutureFuturelow-level; made by the loop or librarieslow-level; loop या libraries बनाती हैं底层对象;由事件循环或库创建a box that is filled with a result laterएक डिब्बा जो बाद में result से भरता है一个稍后会被填入结果的"盒子"
Anything elseबाक़ी कुछ भी其他任何东西await 5TypeError — not awaitableTypeError — awaitable नहींTypeError — 不可等待
import asyncio

async def main():
    loop = asyncio.get_running_loop()

    fut = loop.create_future()                      # 1) a Future
    loop.call_later(0.1, fut.set_result, "filled by the loop")
    print(await fut)

    task = asyncio.create_task(asyncio.sleep(0.1, result="task result"))
    print(await task)                               # 2) a Task

    print(await asyncio.sleep(0.1, result="coroutine result"))   # 3) a coroutine

    try:
        await 5                                     # not awaitable
    except TypeError as e:
        print("TypeError:", e)

asyncio.run(main())

try:
    compile("import asyncio\nawait asyncio.sleep(1)", "demo.py", "exec")
except SyntaxError as e:
    print("SyntaxError:", e.msg)
OUTPUT
filled by the loop task result coroutine result TypeError: object int can't be used in 'await' expression SyntaxError: 'await' outside function
⚠️
await Only Works Inside async defawait सिर्फ़ async def के अंदर चलता हैawait 只能在 async def 内使用

As the last line shows, await at the top level of a normal .py file is a SyntaxError. Normal code starts the loop with asyncio.run(); inside async code you use await. (The interactive python -m asyncio REPL and Jupyter allow top-level await as a convenience.)

जैसा आख़िरी line दिखाती है, सामान्य .py file के top level पर await एक SyntaxError है। सामान्य code asyncio.run() से loop शुरू करता है; async code के अंदर आप await लेते हैं। (Interactive python -m asyncio REPL और Jupyter सुविधा के लिए top-level await की अनुमति देते हैं।)

如最后一行所示,在普通 .py 文件的顶层写 await 会导致 SyntaxError。普通代码用 asyncio.run() 启动事件循环;在异步代码内部才使用 await。(交互式的 python -m asyncio REPL 和 Jupyter 为了方便允许顶层 await。)

⏱️ Simulator: Await Patterns Lab⏱️ Simulator: Await Patterns Lab⏱️ 模拟器:await 模式实验室

Three jobs: A and B each wait 0.5 s, C is a 0.5 s wait or a 1 s CPU loop. Pick how they are awaited. Each timeline is a real measured run: grey = waiting at await, red = finished waiting but stuck because the loop is busy, solid = using the CPU.

तीन jobs: A और B हर एक 0.5 s इंतज़ार करते हैं, C 0.5 s इंतज़ार या 1 s का CPU loop है। चुनिए उन्हें कैसे await किया जाए। हर timeline एक असली मापा हुआ run है: धूसर = await पर इंतज़ार, लाल = इंतज़ार पूरा पर loop व्यस्त होने से अटका, गाढ़ा = CPU इस्तेमाल।

三个任务:A 和 B 各等待 0.5 秒,C 是 0.5 秒等待或 1 秒的 CPU 循环。选择 await 它们的方式。每条时间线都是真实测量的运行:灰色 = 在 await 处等待,红色 = 等待已结束却因事件循环忙碌而 卡住,实色 = 占用 CPU。

Await Patterns LabAwait Patterns Labawait 模式实验室5 real runs5 असली runs5 次真实运行

SectionSection第 04 节

Inside the Event Loop 🔄Event Loop के अंदर 🔄事件循环内部 🔄

Figure 3 — The Event Loop Scheduling Four Coroutinesचित्र 3 — Event Loop चार Coroutines को चलाता हुआ图 3 — 事件循环调度四个协程
The event loop scheduling coroutines🔄 event loopone threadएक thread单线程coroutine ARUNNINGcoroutine Bwaiting: socketcoroutine Cwaiting: timercoroutine DREADYonly ONE coroutine runs at any moment; the others wait or are readyकिसी भी पल सिर्फ़ एक coroutine चलता है; बाक़ी इंतज़ार में या तैयार任何时刻只有一个协程在运行;其他的在等待或已就绪

One coroutine runs; the others are suspended until their I/O or timer is ready.

एक coroutine चलता है; बाक़ी अपने I/O या timer के तैयार होने तक रुके रहते हैं।

一个协程在运行;其他协程挂起,直到它们的 I/O 或定时器就绪。

🔄 What happens, step by stepक़दम-दर-क़दम क्या होता है一步一步发生了什么
01
You call asyncio.run(main()). Python creates an event loop, schedules main() and starts the loop.
आप asyncio.run(main()) बुलाते हैं। Python एक event loop बनाता है, main() को schedule करता है और loop शुरू करता है।
你调用 asyncio.run(main())。Python 创建一个事件循环,安排 main(),然后启动循环。
02
The loop runs a ready coroutine until it reaches an await that has to wait. Nothing interrupts a coroutine in the middle of plain code.
Loop एक तैयार coroutine चलाता है जब तक वह किसी ऐसे await तक न पहुँचे जिसे इंतज़ार करना हो। सादे code के बीच में coroutine को कुछ नहीं रोकता।
事件循环运行一个就绪的协程,直到它遇到一个需要等待的 await。普通代码执行到一半时,协程不会被打断。
03
At the await, the coroutine is suspended. It registers "wake me when this timer / reply arrives" and hands control back. Its local variables are kept.
Await पर coroutine रुक जाता है। यह दर्ज करता है "यह timer / जवाब आए तो मुझे जगाना" और control वापस देता है। इसके local variables बचे रहते हैं।
遇到 await 时,协程被挂起。它登记"当这个定时器 / 回复到来时叫醒我",然后交还控制权。它的局部变量会被保留。
04
The loop runs the next ready coroutine. While A waits, B, C, D get their turns. This is how one thread juggles thousands of connections.
Loop अगला तैयार coroutine चलाता है। जब A इंतज़ार करता है, B, C, D की बारी आती है। ऐसे ही एक thread हज़ारों connections संभालता है।
事件循环运行下一个就绪的协程。A 在等待时,B、C、D 轮流运行。一个线程就是这样同时照顾成千上万个连接的。
05
An event arrives → the coroutine resumes exactly where it stopped.
Event आता है → coroutine ठीक वहीं से फिर चलता है जहाँ रुका था।
事件到达 → 协程恢复,从它停下的地方继续。
06
When main() finishes, asyncio.run() cancels any tasks still left, closes the loop and returns main()'s result.
जब main() ख़त्म होता है, asyncio.run() बचे हुए tasks cancel करता है, loop बंद करता है और main() का result लौटाता है।
当 main() 结束时,asyncio.run() 会取消仍未完成的任务,关闭事件循环,并返回 main() 的结果。

🔄 Simulator: Event Loop Stepper🔄 Simulator: Event Loop Stepper🔄 模拟器:事件循环单步演示

Three coroutines run with asyncio.gather(). Step through and watch the ready queue, the waiting timers and the output. Time is in units (1 unit = 30 ms in the real test). The scheduling rules were checked against real asyncio on 300 random programs — press 🎲 for a new one.

तीन coroutines asyncio.gather() से चलते हैं। क़दम-दर-क़दम चलिए और ready queue, waiting timers और output देखिए। समय units में है (असली test में 1 unit = 30 ms)। Scheduling नियम 300 random programs पर असली asyncio से जाँचे गए — नए के लिए 🎲 दबाइए।

三个协程通过 asyncio.gather() 运行。一步一步观察 就绪 队列、等待中 的定时器和输出。时间以单位计(真实测试中 1 单位 = 30 毫秒)。调度规则已在 300 个随机程序上与真实的 asyncio 核对过 — 按 🎲 生成一个新程序。

Event Loop StepperEvent Loop Stepper事件循环单步演示checked on 300 real programs300 असली programs पर जाँचा已用 300 个真实程序验证
OutputOutput输出
⚠️
Single Thread — but Not Free of Racesएक Thread — पर Races से मुक्त नहीं单线程 — 但并非没有竞态

All of this runs on one OS thread, so there is no GIL contention and a coroutine is never interrupted in the middle of plain code. That makes async much easier to reason about than threads.

यह सब एक OS thread पर चलता है, इसलिए GIL की होड़ नहीं होती और coroutine सादे code के बीच में कभी नहीं रुकता। इससे async को समझना threads से बहुत आसान है।

这一切都在一个操作系统线程上运行,所以没有 GIL 争用,协程也永远不会在普通代码中途被打断。这让异步比线程容易理解得多。

🔧
Fixed: "No Race Conditions" Is Too Strongसुधार: "कोई Race Conditions नहीं" बहुत ज़्यादा है已修正:"没有竞态条件"的说法过头了

The original said there are "no race conditions between coroutines". Between two awaits your code is safe. But if a coroutine reads shared data, then awaits, then writes it back, another coroutine can change the data during that pause — the same lost-update problem as with threads, just at clear, visible points. Keep read-modify-write steps on shared data free of await, or protect them.

मूल में कहा गया कि "coroutines के बीच कोई race conditions नहीं"। दो awaits के बीच आपका code सुरक्षित है। पर अगर कोई coroutine साझा data पढ़ता है, फिर await करता है, फिर वापस लिखता है, तो उस रुकाव के दौरान कोई दूसरा coroutine data बदल सकता है — threads वाली ही खोए-update की समस्या, बस साफ़, दिखने वाली जगहों पर। साझा data पर read-modify-write क़दमों को await से मुक्त रखिए, या उन्हें सुरक्षित कीजिए।

原文说协程之间"没有竞态条件"。在两个 await 之间,你的代码是安全的。但如果一个协程读取共享数据,然后 await,再写回,另一个协程就可能在这个暂停期间修改数据 — 和线程一样的丢失更新问题,只是发生在清晰可见的位置。让共享数据上的"读-改-写"步骤中间不出现 await,或者对它们加以保护。


SectionSection第 05 节

Running Things Concurrently — asyncio.gather 🧺चीज़ें एक साथ चलाना — asyncio.gather 🧺并发运行 — asyncio.gather 🧺

A single await on its own gives you nothing — it just waits. The speed-up comes from running several coroutines at once with asyncio.gather().

अकेला await कुछ नहीं देता — बस इंतज़ार करता है। Speed-up asyncio.gather() से कई coroutines एक साथ चलाने से आता है।

单独一个 await 什么好处也没有 — 它只是在等待。加速来自用 asyncio.gather() 同时运行多个协程。

import asyncio, time

async def fetch(name, delay):
    print(f"-> start {name}")
    await asyncio.sleep(delay)      # simulate network
    print(f"OK done  {name}")
    return f"{name}-result"

async def sequential():
    t0 = time.perf_counter()
    a = await fetch("A", 1)         # wait for A
    b = await fetch("B", 1)         # then wait for B
    c = await fetch("C", 1)         # then wait for C
    print(f"Sequential: {time.perf_counter()-t0:.2f}s")

async def concurrent():
    t0 = time.perf_counter()
    a, b, c = await asyncio.gather(
        fetch("A", 1),
        fetch("B", 1),
        fetch("C", 1),
    )
    print(f"Concurrent: {time.perf_counter()-t0:.2f}s")
    print("results:", [a, b, c])

asyncio.run(sequential())
asyncio.run(concurrent())
OUTPUT
-> start A OK done A -> start B OK done B -> start C OK done C Sequential: 3.00s -> start A -> start B -> start C OK done A OK done B OK done C Concurrent: 1.00s results: ['A-result', 'B-result', 'C-result']
Figure 4 — asyncio.gather() Execution Traceचित्र 4 — asyncio.gather() Execution Trace图 4 — asyncio.gather() 执行轨迹
asyncio.gather execution traceAsuspended (await asyncio.sleep(1))Bsuspended (await asyncio.sleep(1))Csuspended (await asyncio.sleep(1))timers firestart

Each task runs briefly, hits await, and waits. All three waits overlap; then each finishes.

हर task थोड़ा चलता है, await पर पहुँचता है, और इंतज़ार करता है। तीनों इंतज़ार साथ-साथ होते हैं; फिर हर एक ख़त्म होता है।

每个任务短暂运行,遇到 await 后开始等待。三个等待相互重叠,然后各自完成。

🏆
Where the Speed-Up Came FromSpeed-Up कहाँ से आया加速从何而来

All three fetch() calls reached await asyncio.sleep(1) almost at once. The loop then had three coroutines waiting on timers that end at about the same moment, and resumed them one after another. Total wall time ≈ the longest single wait, not the sum. gather() also returns the results in the order you passed the coroutines.

तीनों fetch() calls लगभग एक साथ await asyncio.sleep(1) तक पहुँचीं। फिर loop के पास तीन coroutines थे जो लगभग एक ही पल ख़त्म होने वाले timers का इंतज़ार कर रहे थे, और उसने उन्हें एक के बाद एक फिर चलाया। कुल समय ≈ सबसे लंबा अकेला इंतज़ार, जोड़ नहीं। gather() results उसी क्रम में लौटाता है जिसमें आपने coroutines दिए।

三个 fetch() 调用几乎同时到达 await asyncio.sleep(1)。事件循环于是有三个协程在等待几乎同时结束的定时器,然后依次恢复它们。总耗时 ≈ 最长的单次等待,而不是总和。gather() 还会按你传入协程的顺序返回结果。

🏁 Simulator: Sync vs Async Race🏁 Simulator: Sync बनाम Async Race🏁 模拟器:同步 vs 异步赛跑

Each "fetch" waits 0.5 s. Choose how many fetches to run. Top: await one after another. Bottom: asyncio.gather(). Real runs.

हर "fetch" 0.5 s इंतज़ार करता है। चुनिए कितने fetch चलें। ऊपर: एक के बाद एक await। नीचे: asyncio.gather()। असली runs।

每个"fetch"等待 0.5 秒。选择运行多少个 fetch。上面:依次 await。下面:asyncio.gather()。真实运行。

Sync vs Async RaceSync बनाम Async Race同步 vs 异步赛跑8 real runs8 असली runs8 次真实运行
Fetches:Fetches:Fetch 数:
await one after anotherएक के बाद एक await依次 await
await asyncio.gather(...)await asyncio.gather(...)await asyncio.gather(...)

SectionSection第 06 节

Tasks — Start Now, Collect Later 📌Tasks — अभी शुरू, बाद में लें 📌任务 — 现在启动,稍后取结果 📌

asyncio.gather() waits for a fixed set of coroutines. Sometimes you want to start a coroutine, carry on with your own work, and collect the result later. That is a Task: a coroutine wrapped and scheduled on the loop.

asyncio.gather() coroutines के एक तय समूह का इंतज़ार करता है। कभी-कभी आप एक coroutine शुरू करके अपना काम जारी रखना और result बाद में लेना चाहते हैं। यही Task है: loop पर schedule किया गया लिपटा हुआ coroutine।

asyncio.gather() 等待一组固定的协程。有时你想 启动 一个协程,继续做自己的事,稍后再取结果。这就是 Task:一个被包装并安排在事件循环上的协程。

📌 Task lifecycleTask lifecycle任务生命周期
Create
task = asyncio.create_task(coro()) — schedules the coroutine on the loop. It starts running the next time your code reaches an await.
task = asyncio.create_task(coro()) — coroutine को loop पर schedule करता है। यह तब चलना शुरू होता है जब आपका code अगली बार किसी await पर पहुँचता है।
task = asyncio.create_task(coro()) — 把协程安排到事件循环上。下一次你的代码执行到 await 时,它就开始运行。
Runs
The task runs alongside your coroutine, taking turns at every await.
Task आपके coroutine के साथ-साथ चलता है, हर await पर बारी लेता है।
任务与你的协程并行运行,在每个 await 处轮流执行。
Collect
result = await task — waits until the task is done and gives its return value.
result = await task — task पूरा होने तक इंतज़ार करता है और उसकी return value देता है।
result = await task — 等待任务完成并取得它的返回值。
Cancel
task.cancel() — asks the task to stop. A CancelledError is raised inside it at its current await.
task.cancel() — task से रुकने को कहता है। उसके मौजूदा await पर उसके अंदर CancelledError raise होता है।
task.cancel() — 请求任务停止。任务内部会在它当前的 await 处抛出 CancelledError。
import asyncio, time

async def slow_price_lookup(symbol):
    print("  [task] API call started")
    await asyncio.sleep(2)          # pretend this is an API call
    return {"symbol": symbol, "price": 142.35}

async def main():
    t0 = time.perf_counter()
    # Schedule the task - it starts as soon as main() reaches its next await
    task = asyncio.create_task(slow_price_lookup("AAPL"))

    # Meanwhile, do other work - this takes ~1 second
    print("doing local calculations...")
    await asyncio.sleep(1)
    print("calculations done, now waiting on API...")

    # Collect the result. Only ~1 more second of waiting (not 2!)
    result = await task
    print(f"Got: {result}")
    print(f"Total wall time: {time.perf_counter() - t0:.2f}s (not 3)")

asyncio.run(main())
OUTPUT
doing local calculations... [task] API call started calculations done, now waiting on API... Got: {'symbol': 'AAPL', 'price': 142.35} Total wall time: 2.00s (not 3)
🔧
Fixed: create_task() Does Not Start the Task "Right Now"सुधार: create_task() Task को "अभी" शुरू नहीं करता已修正:create_task() 不会"立刻"启动任务

The original comment said the task "starts running NOW". The real output shows otherwise: "doing local calculations..." printed before "[task] API call started". create_task() only schedules the task; it gets its first turn when main() reaches await asyncio.sleep(1). We also printed the total time — 2.00 s, not 3 — which the original only claimed.

मूल comment में कहा गया कि task "अभी चलना शुरू होता है"। असली output कुछ और दिखाता है: "doing local calculations..." "[task] API call started" से पहले print हुआ। create_task() सिर्फ़ task को schedule करता है; उसे पहली बारी तब मिलती है जब main() await asyncio.sleep(1) तक पहुँचता है। हमने कुल समय भी print किया — 2.00 s, 3 नहीं — जिसका मूल ने सिर्फ़ दावा किया था।

原文注释说任务"现在就开始运行"。真实输出显示并非如此:"doing local calculations..."在"[task] API call started"之前 打印。create_task() 只是安排任务;直到 main() 执行到 await asyncio.sleep(1),任务才第一次轮到运行。我们还打印了总耗时 — 2.00 秒,而不是 3 秒 — 原文只是这样声称而已。

Figure 5 — create_task() vs Sequential awaitचित्र 5 — create_task() बनाम Sequential await图 5 — create_task() 与依次 await
create_task vs sequential awaitSEQUENTIAL await — 3 sSEQUENTIAL await — 3 s依次 await — 3 秒APIAPI call 2 slocallocal work 1 screate_task() — 2 screate_task() — 2 screate_task() — 2 秒APIAPI call 2 slocallocal work 1 s← overlaps with the API wait

create_task() lets local work overlap the API wait: 2 s instead of 3 s.

create_task() local काम को API के इंतज़ार के साथ-साथ चलने देता है: 3 s की जगह 2 s।

create_task() 让本地工作与 API 等待重叠:2 秒而不是 3 秒。


SectionSection第 07 节

Practical Example — Fetching Many URLs With aiohttp 🌐व्यावहारिक उदाहरण — aiohttp से कई URLs Fetch करना 🌐实战示例 — 用 aiohttp 获取多个 URL 🌐

This is where async shines. aiohttp is an async HTTP client — the async counterpart of requests. Five URLs that each take 1 second come back in about 1 second together.

यहीं async चमकता है। aiohttp एक async HTTP client है — requests का async साथी। पाँच URLs जो हर एक 1 second लेते हैं, साथ में लगभग 1 second में लौटते हैं।

这正是异步大放异彩的地方。aiohttp 是一个异步 HTTP 客户端 — 相当于 requests 的异步版。五个各需 1 秒的 URL,一起只需约 1 秒就能返回。

# pip install aiohttp
import asyncio, aiohttp, time

URLS = ["http://127.0.0.1:8765/delay/1"] * 5     # our local test server (no internet here)

async def fetch_url(session, url):
    async with session.get(url) as resp:
        return resp.status, len(await resp.text())

async def fetch_all(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, u) for u in urls]
        return await asyncio.gather(*tasks)

t0 = time.perf_counter()
results = asyncio.run(fetch_all(URLS))
print(f"Fetched {len(results)} URLs in {time.perf_counter()-t0:.2f}s")
for status, size in results:
    print(f"  status={status}  bytes={size}")
OUTPUT
Fetched 5 URLs in 1.01s status=200 bytes=123 status=200 bytes=123 status=200 bytes=123 status=200 bytes=123 status=200 bytes=123
🛡️
Always Reuse the SessionSession हमेशा दोबारा इस्तेमाल करें始终复用会话

Create one aiohttp.ClientSession and pass it to every request. A new session per request throws away connection pooling, DNS caching and keep-alive connections — and much of the speed-up with them. The same rule applies to httpx.AsyncClient.

एक aiohttp.ClientSession बनाइए और हर request को दीजिए। हर request पर नया session connection pooling, DNS caching और keep-alive connections फेंक देता है — और उनके साथ speed-up का बड़ा हिस्सा भी। यही नियम httpx.AsyncClient पर लागू है।

只创建一个 aiohttp.ClientSession,并把它传给每个请求。每个请求都新建会话,会丢掉连接池、DNS 缓存和长连接 — 也会丢掉大部分加速效果。httpx.AsyncClient 也是同样的规则。


SectionSection第 08 节

Timeouts and Cancellation ⏱️Timeouts और Cancellation ⏱️超时与取消 ⏱️

Network calls hang. Databases stall. You need a way to say "give up if this takes too long". asyncio.timeout() and asyncio.wait_for() do this cleanly.

Network calls अटक जाती हैं। Databases रुक जाते हैं। आपको कहने का तरीक़ा चाहिए "बहुत देर लगे तो छोड़ दो"। asyncio.timeout() और asyncio.wait_for() यह साफ़ ढंग से करते हैं।

网络调用会卡住,数据库会停滞。你需要一种方式说"太久了就放弃"。asyncio.timeout() 和 asyncio.wait_for() 可以干净地做到这一点。

import asyncio, time

async def slow_task():
    await asyncio.sleep(10)
    return "done"

async def main():
    t0 = time.perf_counter()
    # Modern style - Python 3.11+
    try:
        async with asyncio.timeout(2):
            result = await slow_task()
    except TimeoutError:
        print(f"gave up after {time.perf_counter() - t0:.1f} seconds")

    t0 = time.perf_counter()
    # Classic style - works on older versions too
    try:
        result = await asyncio.wait_for(slow_task(), timeout=2)
    except asyncio.TimeoutError:              # same class as TimeoutError since 3.11
        print(f"also gave up after {time.perf_counter() - t0:.1f} seconds")

asyncio.run(main())
OUTPUT
gave up after 2.0 seconds also gave up after 2.0 seconds
🔑
One Exception Classएक ही Exception Class同一个异常类

Since Python 3.11, asyncio.TimeoutError is simply another name for the built-in TimeoutError, so either except works with both styles. On older versions they were different classes — catch asyncio.TimeoutError there.

Python 3.11 से asyncio.TimeoutError built-in TimeoutError का बस दूसरा नाम है, इसलिए दोनों तरीक़ों के साथ कोई भी except चलता है। पुराने versions में ये अलग classes थीं — वहाँ asyncio.TimeoutError पकड़िए।

从 Python 3.11 起,asyncio.TimeoutError 只是内置 TimeoutError 的另一个名字,所以两种写法都可以用任意一个 except。在旧版本中它们是不同的类 — 那时请捕获 asyncio.TimeoutError。

Cancelling a task by handTask को हाथ से cancel करना手动取消任务

import asyncio

async def worker(n):
    try:
        while True:
            print(f"worker {n} tick")
            await asyncio.sleep(0.5)
    except asyncio.CancelledError:
        print(f"worker {n} shutting down cleanly")
        raise                        # re-raise so cancellation propagates

async def main():
    t = asyncio.create_task(worker(1))
    await asyncio.sleep(2)
    t.cancel()                       # request cancellation
    try:
        await t
    except asyncio.CancelledError:
        print("main: the task is cancelled ->", t.cancelled())

asyncio.run(main())
OUTPUT
worker 1 tick worker 1 tick worker 1 tick worker 1 tick worker 1 shutting down cleanly main: the task is cancelled -> True

The worker ticked 4 times in 2 seconds, then CancelledError appeared at its await. It cleaned up and re-raised, so the task really ended — t.cancelled() is True. The Bug Doctor (Section 15) shows what happens if you swallow the error instead.

Worker ने 2 seconds में 4 बार tick किया, फिर उसके await पर CancelledError आया। उसने सफ़ाई की और फिर raise किया, इसलिए task सच में ख़त्म हुआ — t.cancelled() True है। Bug Doctor (Section 15) दिखाता है कि error निगल जाने पर क्या होता है।

工作协程在 2 秒内 tick 了 4 次,然后 CancelledError 在它的 await 处出现。它做完清理并 重新抛出,所以任务真正结束了 — t.cancelled() 为 True。Bug 医生(第 15 节)展示了如果吞掉这个错误会发生什么。


SectionSection第 09 节

Common Pitfalls (and How to Avoid Them) 🩹आम ग़लतियाँ (और उनसे कैसे बचें) 🩹常见陷阱(以及如何避免)🩹

Mistakeग़लती错误What happensक्या होता है会发生什么FixFix修复
time.sleep(2) inside asyncasync के अंदर在异步代码中Freezes the whole event loop for 2 sपूरे event loop को 2 s के लिए जमा देता है冻结整个事件循环 2 秒await asyncio.sleep(2)
Calling requests.get() in asyncAsync में requests.get() बुलाना在异步代码中调用 requests.get()Blocks the loop — no concurrencyLoop को रोकता है — कोई concurrency नहीं阻塞循环 — 失去并发aiohttp / httpx.AsyncClient, or asyncio.to_thread()aiohttp / httpx.AsyncClient, या asyncio.to_thread()aiohttp / httpx.AsyncClient,或 asyncio.to_thread()
Forgetting awaitawait भूलना忘记 awaitThe coroutine never runs; Python only prints a RuntimeWarningCoroutine कभी नहीं चलता; Python बस एक RuntimeWarning print करता है协程从不运行;Python 只打印一条 RuntimeWarningAwait every coroutine call (or wrap it in a task)हर coroutine call को await कीजिए (या task में लपेटिए)await 每个协程调用(或包装成任务)
CPU-heavy math inside asyncAsync के अंदर भारी गणना在异步代码中做繁重计算Starves all other coroutinesबाक़ी सभी coroutines को भूखा रखता है让其他所有协程"饿死"Offload: ProcessPoolExecutor (or to_thread for C code that releases the GIL)बाहर भेजिए: ProcessPoolExecutor (या GIL छोड़ने वाले C code के लिए to_thread)转移出去:ProcessPoolExecutor(或对释放 GIL 的 C 代码用 to_thread)
asyncio.run() inside a coroutineCoroutine के अंदर asyncio.run()在协程内部调用 asyncio.run()RuntimeError: asyncio.run() cannot be called from a running event loopInside async code, just awaitAsync code के अंदर बस await कीजिए在异步代码中直接 await
No shared session for HTTPHTTP के लिए साझा session नहींHTTP 没有共享会话No connection pooling — much slowerConnection pooling नहीं — बहुत धीमा没有连接池 — 慢得多One ClientSession, reusedएक ClientSession, बार-बार इस्तेमाल一个 ClientSession,反复复用
Task created and forgottenTask बनाकर भूल जाना创建任务后忘记It can be garbage-collected mid-run, and its errors go unseenयह बीच में garbage-collect हो सकता है, और इसके errors नहीं दिखते它可能在运行中途被垃圾回收,而且它的错误无人察觉Keep references and await themReferences रखिए और उन्हें await कीजिए保留引用并 await 它们
🔧
Fixed: Two Rows of the Original Table Were Wrongसुधार: मूल Table की दो Rows ग़लत थीं已修正:原表格中有两行是错的

The original said forgetting await is a "silent bug". Python does print RuntimeWarning: coroutine 'save' was never awaited (real run below) — easy to miss, but not silent. It also said "multiple asyncio.run() calls" lose state. Calling it several times one after another is fine (Section 05 does exactly that). The real mistake is calling it inside a running loop, which raises RuntimeError.

मूल में कहा गया कि await भूलना "चुप bug" है। Python RuntimeWarning: coroutine 'save' was never awaited print करता है (नीचे असली run) — छूट जाना आसान, पर चुप नहीं। यह भी कहा गया कि "कई asyncio.run() calls" state खो देती हैं। इसे एक के बाद एक कई बार बुलाना ठीक है (Section 05 ठीक यही करता है)। असली ग़लती इसे चलते loop के अंदर बुलाना है, जो RuntimeError raise करता है।

原文说忘记 await 是"无声的 bug"。Python 实际上会打印 RuntimeWarning: coroutine 'save' was never awaited(见下面的真实运行)— 容易被忽略,但并非无声。原文还说"多次调用 asyncio.run()"会丢失状态。依次 多次调用它是没问题的(第 05 节正是这样做的)。真正的错误是在正在运行的事件循环 内部 调用它,这会抛出 RuntimeError。

Real runs of three mistakesतीन ग़लतियों के असली runs三个错误的真实运行

import asyncio, time

async def ticker():
    t0 = time.perf_counter()
    for _ in range(4):
        print(f"tick at {time.perf_counter() - t0:.1f}s")
        await asyncio.sleep(0.5)

async def bad_worker():
    time.sleep(2)                    # BLOCKING sleep inside async code!

async def main():
    await asyncio.gather(ticker(), bad_worker())

asyncio.run(main())
OUTPUT
tick at 0.0s tick at 2.0s tick at 2.5s tick at 3.0s

The ticker should tick every 0.5 s. Instead there is a 2-second gap: time.sleep(2) froze the whole loop.

Ticker को हर 0.5 s पर tick करना चाहिए। इसकी जगह 2-second का gap है: time.sleep(2) ने पूरा loop जमा दिया।

ticker 本应每 0.5 秒 tick 一次。结果却出现了 2 秒的空档:time.sleep(2) 冻结了整个事件循环。

import asyncio

async def save(item):
    print("saving", item)

async def main():
    save("order-42")                 # forgot await!
    print("main finished")

asyncio.run(main())
OUTPUT
demo.py:7: RuntimeWarning: coroutine 'save' was never awaited save("order-42") # forgot await! RuntimeWarning: Enable tracemalloc to get the object allocation traceback main finished
import asyncio

async def helper():
    return 42

async def main():
    print(asyncio.run(helper()))     # asyncio.run() INSIDE a running loop

asyncio.run(main())
OUTPUT
Traceback (most recent call last): ... File "asyncio/runners.py", line 192, in run raise RuntimeError( "asyncio.run() cannot be called from a running event loop") RuntimeError: asyncio.run() cannot be called from a running event loop <sys>:0: RuntimeWarning: coroutine 'helper' was never awaited

The fix for blocking libraries — asyncio.to_thread()Blocking libraries का इलाज — asyncio.to_thread()阻塞库的解决办法 — asyncio.to_thread()

import asyncio, time

def blocking_download():             # a normal (sync) library call
    time.sleep(1)
    return "data"

async def main():
    t0 = time.perf_counter()
    results = await asyncio.gather(
        asyncio.to_thread(blocking_download),   # runs in a worker thread
        asyncio.to_thread(blocking_download),
        asyncio.to_thread(blocking_download),
    )
    print(results, f"in {time.perf_counter() - t0:.2f}s")

asyncio.run(main())
OUTPUT
['data', 'data', 'data'] in 1.00s

Three blocking 1-second calls finished in about 1 second: each ran in a worker thread while the event loop stayed free.

तीन blocking 1-second calls लगभग 1 second में पूरी हुईं: हर एक worker thread में चली जबकि event loop ख़ाली रहा।

三个各阻塞 1 秒的调用约 1 秒就全部完成:每个都在工作线程中运行,而事件循环保持空闲。

🧊 Simulator: Blocking Lab🧊 Simulator: Blocking Lab🧊 模拟器:阻塞实验室

A ticker should tick every 0.25 s (hollow circles). A worker starts at 0.3 s and does 1 second of "work" in one of four ways. Filled circles are the real tick times.

एक ticker को हर 0.25 s पर tick करना चाहिए (खोखले circles)। एक worker 0.3 s पर शुरू होकर चार में से एक तरीक़े से 1 second का "काम" करता है। भरे circles असली tick समय हैं।

一个 ticker 应该每 0.25 秒 tick 一次(空心圆)。一个工作协程在 0.3 秒时开始,用四种方式之一做 1 秒的"工作"。实心圆是真实的 tick 时间。

Blocking LabBlocking Lab阻塞实验室4 real runs4 असली runs4 次真实运行
⚠️
The Golden Sin — Blocking Inside Asyncसबसे बड़ा पाप — Async के अंदर Blocking头号大忌 — 在异步中阻塞

One time.sleep(), one requests.get() or one long pandas operation inside an async def freezes every other coroutine until it finishes. Async is cooperative — everyone must yield. When you must call a blocking library, wrap it: await asyncio.to_thread(blocking_fn, args). For heavy pure-Python CPU work, use a process pool instead.

async def के अंदर एक time.sleep(), एक requests.get() या एक लंबा pandas operation हर दूसरे coroutine को उसके ख़त्म होने तक जमा देता है। Async cooperative है — सबको बारी छोड़नी होती है। जब blocking library बुलानी ही हो, उसे लपेटिए: await asyncio.to_thread(blocking_fn, args)। भारी pure-Python CPU काम के लिए process pool लीजिए।

在 async def 中执行一次 time.sleep()、一次 requests.get() 或一次耗时的 pandas 操作,都会冻结其他所有协程直到它结束。异步是协作式的 — 每个人都必须让出控制权。必须调用阻塞库时,把它包装起来:await asyncio.to_thread(blocking_fn, args)。对于繁重的纯 Python CPU 工作,请改用进程池。


SectionSection第 10 节

async / await vs Threads vs Processes ⚖️async / await बनाम Threads बनाम Processes ⚖️async / await vs 线程 vs 进程 ⚖️

Threads and async/await both let one program handle many waiting jobs. They look similar from far away, but they work in very different ways. The key difference is who decides when to switch.

Threads और async/await दोनों एक program को कई इंतज़ार करते jobs संभालने देते हैं। दूर से दोनों एक जैसे दिखते हैं, पर बहुत अलग ढंग से काम करते हैं। मुख्य अंतर है switch कब करना है, यह कौन तय करता है।

线程和 async/await 都能让一个程序处理许多等待中的任务。远看相似,但工作方式截然不同。关键区别在于 由谁决定何时切换。

🧵 Threads — the OS decidesThreads — OS तय करता है线程 — 由操作系统决定
Questionसवाल问题ThreadsThreads线程
Who switches?Switch कौन करता है?谁来切换?The OS (and the GIL), at any momentOS (और GIL), किसी भी पल操作系统(和 GIL),任何 时刻
How many run Python at once?एक साथ कितने Python चलाते हैं?同时有几个在运行 Python?One at a time (GIL)एक बार में एक (GIL)一次一个(GIL)
Cost of each oneहर एक की लागत每个的开销A real OS thread with its own stackअपने stack वाला असली OS thread一个带独立栈的真实操作系统线程
Normal blocking code?सामान्य blocking code?普通阻塞代码?Yes — requests, time.sleep just workहाँ — requests, time.sleep सीधे चलते हैं可以 — requests、time.sleep 直接可用
Shared dataसाझा data共享数据Needs locks almost everywhereलगभग हर जगह locks चाहिए几乎处处需要锁
Stop one early?एक को जल्दी रोकना?提前停止某个?No built-in wayकोई built-in तरीका नहीं没有内置办法
⚡ async/await — your code decidesasync/await — आपका code तय करता हैasync/await — 由你的代码决定
Questionसवाल问题async/awaitasync/awaitasync/await
Who switches?Switch कौन करता है?谁来切换?The event loop, only at awaitEvent loop, सिर्फ़ await पर事件循环,只在 await 处
How many run Python at once?एक साथ कितने Python चलाते हैं?同时有几个在运行 Python?One (a single thread)एक (एक ही thread)一个(单线程)
Cost of each oneहर एक की लागत每个的开销A small Python objectएक छोटा Python object一个小小的 Python 对象
Normal blocking code?सामान्य blocking code?普通阻塞代码?No — it freezes the loop; needs async librariesनहीं — loop जम जाता है; async libraries चाहिए不行 — 会卡住事件循环;需要异步库
Shared dataसाझा data共享数据Safe between two awaits; careful across an awaitदो awaits के बीच सुरक्षित; await के पार सावधानी两个 await 之间安全;跨越 await 时要小心
Stop one early?एक को जल्दी रोकना?提前停止某个?Yes — task.cancel()हाँ — task.cancel()可以 — task.cancel()
Figure 5b — Who Decides When to Switch?चित्र 5b — Switch कब हो, यह कौन तय करता है?图 5b — 谁决定何时切换?
Switch points: threads vs asyncTHREADS — the OS can switch anywhereTHREADS — OS कहीं भी switch कर सकता है线程 — 操作系统可在任意处切换T1T2red lines = switches chosen by the OS — even between "read" and "write"लाल रेखाएँ = OS के चुने switch — "read" और "write" के बीच भी红线 = 操作系统选择的切换点 — 甚至在"读"和"写"之间ASYNC — switches happen only at awaitASYNC — switch सिर्फ़ await पर异步 — 只在 await 处切换awaitawaitawaitawaitawaittask 1task 2green lines = switches at the awaits YOU wrote — nowhere elseहरी रेखाएँ = आपके लिखे awaits पर switch — और कहीं नहीं绿线 = 在你写下的 await 处切换 — 别处不会

Threads are switched by the OS; tasks switch only at await.

Threads को OS switch करता है; tasks सिर्फ़ await पर switch होते हैं।

线程由操作系统切换;任务只在 await 处切换。

Same job, both waysएक ही काम, दोनों तरीकों से同一件事,两种做法

import asyncio, threading, time
from concurrent.futures import ThreadPoolExecutor

N = 100

def blocking_wait(i):                 # thread version - normal blocking code
    time.sleep(0.5)
    return threading.current_thread().name

async def async_wait(i):              # async version - must use await
    await asyncio.sleep(0.5)
    return threading.current_thread().name

t0 = time.perf_counter()
with ThreadPoolExecutor(max_workers=N) as pool:
    names = set(pool.map(blocking_wait, range(N)))
print(f"threads : {time.perf_counter() - t0:.2f}s using {len(names)} OS threads")

async def main():
    return await asyncio.gather(*(async_wait(i) for i in range(N)))

t0 = time.perf_counter()
names = set(asyncio.run(main()))
print(f"asyncio : {time.perf_counter() - t0:.2f}s using {len(names)} thread: {names.pop()}")
OUTPUT
threads : 0.52s using 100 OS threads asyncio : 0.50s using 1 thread: MainThread

Both finish in about half a second — for 100 waiting jobs they are equally fast. The difference is the cost: 100 OS threads versus one thread running 100 small tasks. The lab below raises the number.

दोनों लगभग आधे सेकंड में पूरे होते हैं — 100 इंतज़ार करते jobs के लिए दोनों बराबर तेज़ हैं। अंतर लागत में है: 100 OS threads बनाम एक thread जो 100 छोटे tasks चलाता है। नीचे का lab संख्या बढ़ाता है।

两者都在约半秒内完成 — 对于 100 个等待任务,它们一样快。区别在于开销:100 个操作系统线程,对比一个线程运行 100 个小任务。下面的实验室会增大数量。

📈 Simulator: Threads vs asyncio at Scale📈 Simulator: बड़े पैमाने पर Threads बनाम asyncio📈 模拟器:大规模下的线程 vs asyncio

Threads vs asyncio at Scaleबड़े पैमाने पर Threads बनाम asyncio大规模下的线程 vs asyncioreal runs · each job waits 0.5 sअसली runs · हर job 0.5 s रुकता है真实运行 · 每个任务等待 0.5 秒
jobs:jobs:任务数:

Shared data — where switches can hurtसाझा data — जहाँ switch नुक़सान कर सकता है共享数据 — 切换可能造成伤害的地方

Each worker reads counter, then writes counter + 1. If another worker runs between the read and the write, an update is lost. Threads can be switched there at any time. A task can only be switched there if you put an await there.

हर worker counter पढ़ता है, फिर counter + 1 लिखता है। अगर पढ़ने और लिखने के बीच कोई दूसरा worker चल जाए, तो एक update खो जाता है। Threads वहाँ कभी भी switch हो सकते हैं। Task वहाँ तभी switch हो सकता है जब आप वहाँ await रखें।

每个 worker 先读取 counter,再写入 counter + 1。如果在读和写之间另一个 worker 运行了,就会丢失一次更新。线程随时可能在那里被切换。而任务只有在你在那里放了 await 时才会被切换。

import asyncio, threading, time

def run_threads():
    global counter
    counter = 0
    def add():
        global counter
        for _ in range(1000):
            v = counter            # read
            time.sleep(0)          # the OS may switch threads here (or anywhere)
            counter = v + 1        # write
    ts = [threading.Thread(target=add) for _ in range(4)]
    for t in ts: t.start()
    for t in ts: t.join()
    return counter

async def add_no_await():
    global counter
    for _ in range(1000):
        v = counter
        counter = v + 1            # no await between read and write

async def add_with_await():
    global counter
    for _ in range(1000):
        v = counter
        await asyncio.sleep(0)     # a pause point between read and write!
        counter = v + 1

async def run_tasks(fn):
    global counter
    counter = 0
    await asyncio.gather(*(fn() for _ in range(4)))
    return counter

print("expected            :", 4 * 1000)
print("4 threads           :", run_threads())
print("4 tasks, no await   :", asyncio.run(run_tasks(add_no_await)))
print("4 tasks, with await :", asyncio.run(run_tasks(add_with_await)))
OUTPUT
expected : 4000 4 threads : 1002 4 tasks, no await : 4000 4 tasks, with await : 1000

🧮 Simulator: Lost Update Lab🧮 Simulator: Lost Update Lab🧮 模拟器:丢失更新实验室

Lost Update LabLost Update Lab丢失更新实验室9 real runs · 1000 adds per worker9 असली runs · हर worker 1000 बार जोड़ता है9 次真实运行 · 每个 worker 加 1000 次
runs as:कैसे चलता है:运行方式:
workers:workers:worker 数:

Stopping work earlyकाम जल्दी रोकना提前停止工作

import asyncio, threading, time

async def worker():
    try:
        await asyncio.sleep(10)
    except asyncio.CancelledError:
        print("task: cancelled at its await - cleaning up")
        raise

async def main():
    t = asyncio.create_task(worker())
    await asyncio.sleep(0.1)
    t.cancel()                                 # tasks can be cancelled
    try:
        await t
    except asyncio.CancelledError:
        print("task cancelled after 0.1 s")

asyncio.run(main())

th = threading.Thread(target=time.sleep, args=(1,))
th.start()
print("thread has .cancel()? ", hasattr(th, "cancel"))   # no way to stop it
t0 = time.perf_counter(); th.join()
print(f"had to wait {time.perf_counter() - t0:.1f}s for the thread to finish")
OUTPUT
task: cancelled at its await - cleaning up task cancelled after 0.1 s thread has .cancel()? False had to wait 1.0s for the thread to finish

A task can be cancelled: the CancelledError appears at its await, so it can clean up. A thread has no cancel method — you must build your own stop signal (like an Event) or wait for it to finish.

Task को cancel किया जा सकता है: CancelledError उसके await पर आता है, ताकि वह सफ़ाई कर सके। Thread में कोई cancel method नहीं है — आपको अपना stop signal (जैसे Event) बनाना होगा या उसके पूरा होने का इंतज़ार करना होगा।

任务可以被取消:CancelledError 会在它的 await 处出现,因此它能做清理。线程没有 cancel 方法 — 你必须自己做停止信号(例如 Event),或者等它结束。

All three side by sideतीनों साथ-साथ三者并列比较

Approachतरीक़ा方式Best forकिसके लिए最适合How it switchesकैसे बदलता है如何切换Cost per taskहर task की क़ीमत每个任务的开销
async / awaitI/O-bound (network, DB, files)I/O-bound (network, DB, files)I/O 密集(网络、数据库、文件)Cooperative — only at await, 1 threadCooperative — सिर्फ़ await पर, 1 thread协作式 — 只在 await 处切换,单线程About a KB — huge numbers possibleलगभग एक KB — बहुत बड़ी संख्या संभव约 1 KB — 可以有海量任务
ThreadsThreads线程I/O-bound with blocking librariesBlocking libraries वाला I/O-bound使用阻塞库的 I/O 密集任务Pre-emptive — the OS switches at any time (GIL)Pre-emptive — OS कभी भी बदलता है (GIL)抢占式 — 操作系统随时切换(受 GIL 限制)An OS thread each — hundreds to thousandsहर एक का OS thread — सैकड़ों से हज़ारों每个一个操作系统线程 — 数百到数千
MultiprocessingCPU-bound (math, ML, images)CPU-bound (गणित, ML, images)CPU 密集(数学、机器学习、图像)True parallel — separate processesअसली parallel — अलग processes真正并行 — 独立进程Heavy — usually one per CPU coreभारी — आम तौर पर हर CPU core पर एक开销大 — 通常每个 CPU 核心一个
🎯
Rule of Thumbअंगूठे का नियम经验法则

Does your program wait on outside things? → async / await. Does it crunch numbers or images in Python? → multiprocessing. Must you call blocking libraries you cannot replace? → threads or asyncio.to_thread().

क्या आपका program बाहरी चीज़ों का इंतज़ार करता है? → async / await। क्या वह Python में numbers या images पर गणना करता है? → multiprocessing। क्या आपको ऐसी blocking libraries बुलानी हैं जिन्हें बदल नहीं सकते? → threads या asyncio.to_thread()।

程序在等待外部事物?→ async / await。在用 Python 处理数字或图像?→ 多进程。必须调用无法替换的阻塞库?→ 线程 或 asyncio.to_thread()。


SectionSection第 11 节

Real-World Example — Weather Dashboard Aggregator 🌦️असली उदाहरण — Weather Dashboard Aggregator 🌦️实战示例 — 天气看板聚合器 🌦️

Fetch the current weather for 5 cities at the same time, handle failures, apply a timeout, and print a summary. (Our local server answers like Open-Meteo, with fixed test values and slightly different delays per city.)

5 शहरों का मौजूदा मौसम एक साथ fetch कीजिए, failures संभालिए, timeout लगाइए, और summary print कीजिए। (हमारा local server Open-Meteo की तरह जवाब देता है, तय test values और हर शहर के लिए थोड़ी अलग देरी के साथ।)

同时获取 5 个城市的当前天气,处理失败,设置超时,并打印摘要。(我们的本地服务器以 Open-Meteo 的格式应答,返回固定的测试值,每个城市的延迟略有不同。)

import asyncio, aiohttp, time

CITIES = [
    ("Mumbai",     19.0760, 72.8777),
    ("New York",   40.7128, -74.0060),
    ("London",     51.5074, -0.1278),
    ("Tokyo",      35.6762, 139.6503),
    ("Sydney",    -33.8688, 151.2093),
]
API = "http://127.0.0.1:8765/v1/forecast"   # local stand-in for api.open-meteo.com

async def fetch_weather(session, city, lat, lon):
    params = {"latitude": lat, "longitude": lon, "current_weather": "true"}
    try:
        async with asyncio.timeout(5):
            async with session.get(API, params=params) as r:
                r.raise_for_status()
                data = await r.json()
                cw = data["current_weather"]
                return city, cw["temperature"], cw["windspeed"], None
    except Exception as e:
        return city, None, None, repr(e)

async def main():
    t0 = time.perf_counter()
    async with aiohttp.ClientSession() as session:
        results = await asyncio.gather(
            *[fetch_weather(session, c, lat, lon) for c, lat, lon in CITIES]
        )
    print(f"\nWeather report - fetched in {time.perf_counter()-t0:.2f}s")
    print("-" * 50)
    for city, temp, wind, err in results:
        if err:
            print(f"{city:12s}  ERROR: {err}")
        else:
            print(f"{city:12s}  {temp:5.1f}°C   wind {wind:.1f} km/h")

asyncio.run(main())
OUTPUT
Weather report - fetched in 0.81s -------------------------------------------------- Mumbai 28.4°C wind 12.3 km/h New York 15.1°C wind 8.7 km/h London 11.6°C wind 6.2 km/h Tokyo 22.8°C wind 10.1 km/h Sydney 19.4°C wind 15.5 km/h
🏆
Five Calls, Under One Secondपाँच Calls, एक Second से कम五次调用,不到一秒

The server took 0.4–0.8 s per city. One after another that adds up to 3 s; with gather() and one shared session it took about 0.8 s — the slowest single call. Each call has its own try/except and timeout, so one failing city returns an error row instead of breaking the whole report. We also added r.raise_for_status(), so an HTTP error page is reported as an error instead of crashing on data["current_weather"].

Server ने हर शहर के लिए 0.4–0.8 s लिए। एक के बाद एक यह 3 s बनता; gather() और एक साझा session के साथ लगभग 0.8 s लगे — सबसे धीमी अकेली call। हर call का अपना try/except और timeout है, इसलिए एक शहर fail होने पर पूरी report टूटने की जगह एक error row लौटती है। हमने r.raise_for_status() भी जोड़ा, ताकि HTTP error page data["current_weather"] पर crash होने की जगह error के रूप में दिखे।

服务器对每个城市需要 0.4–0.8 秒。依次请求加起来是 3 秒;用 gather() 加一个共享会话,只用了约 0.8 秒 — 也就是最慢的那一次调用。每个调用都有自己的 try/except 和 timeout,所以某个城市失败时只会返回一行错误,而不会让整个报告崩溃。我们还加了 r.raise_for_status(),让 HTTP 错误页被报告为错误,而不是在 data["current_weather"] 处崩溃。


SectionSection第 12 节

Limiting Concurrency — Don't Overload Your Own API 🚦Concurrency सीमित करना — अपनी ही API पर बोझ मत डालिए 🚦限制并发 — 别把自己的 API 压垮 🚦

Firing 10,000 requests at once is easy in async — and a quick way to get rate-limited or crash the server. Use asyncio.Semaphore to cap how many run at the same time. (We used 200 URLs against our local server.)

Async में 10,000 requests एक साथ भेजना आसान है — और rate-limit होने या server crash करने का तेज़ तरीक़ा भी। एक साथ कितने चलें, इसकी सीमा asyncio.Semaphore से लगाइए। (हमने अपने local server पर 200 URLs लिए।)

在异步中一次发出 10,000 个请求很容易 — 这也是被限流或压垮服务器的捷径。用 asyncio.Semaphore 限制同时运行的数量。(我们对本地服务器使用了 200 个 URL。)

import asyncio, aiohttp, time

in_flight = peak = 0

async def fetch_one(sem, session, url):
    global in_flight, peak
    async with sem:                          # at most N in flight
        in_flight += 1; peak = max(peak, in_flight)
        async with session.get(url) as r:
            text = await r.text()
        in_flight -= 1
        return text

async def fetch_many(urls, max_concurrent=10):
    sem = asyncio.Semaphore(max_concurrent)
    async with aiohttp.ClientSession() as session:
        return await asyncio.gather(
            *[fetch_one(sem, session, u) for u in urls]
        )

urls = [f"http://127.0.0.1:8765/anything/{i}" for i in range(200)]   # each takes ~0.05 s
t0 = time.perf_counter()
results = asyncio.run(fetch_many(urls, max_concurrent=20))
print(f"{len(results)} responses in {time.perf_counter() - t0:.2f}s, "
      f"never more than {peak} in flight")
OUTPUT
200 responses in 0.54s, never more than 20 in flight

We added a small counter to prove the limit: never more than 20 requests were in flight. 200 requests × 0.05 s, 20 at a time → about 10 rounds → about 0.5 s.

हमने सीमा साबित करने के लिए एक छोटा counter जोड़ा: एक साथ कभी 20 से ज़्यादा requests नहीं चलीं। 200 requests × 0.05 s, एक बार में 20 → लगभग 10 rounds → लगभग 0.5 s।

我们加了一个小计数器来 证明 这个限制:同时在途的请求从未超过 20 个。200 个请求 × 0.05 秒,每次 20 个 → 约 10 轮 → 约 0.5 秒。

Figure 6 — Semaphore Gate (max 3)चित्र 6 — Semaphore Gate (max 3)图 6 — 信号量闸门(最多 3 个)
Semaphore gatewaiting tasks🚦 semmax 3in flight #1in flight #2in flight #3leaving "async with sem:" frees a slot for the next task"async with sem:" से निकलते ही अगले task के लिए जगह ख़ाली离开"async with sem:"时就为下一个任务腾出一个名额

async with sem: takes a slot and gives it back automatically when the block ends.

async with sem: एक slot लेता है और block ख़त्म होने पर अपने-आप लौटा देता है।

async with sem: 占用一个名额,代码块结束时自动归还。

🚦 Simulator: Semaphore Lab🚦 Simulator: Semaphore Lab🚦 模拟器:信号量实验室

12 jobs of 0.3 s each, with different limits. Each row is one "slot" of the semaphore. Real runs.

हर एक 0.3 s के 12 jobs, अलग-अलग सीमाओं के साथ। हर row semaphore का एक "slot" है। असली runs।

12 个各 0.3 秒的任务,设置不同的限制。每一行是信号量的一个"名额"。真实运行。

Semaphore LabSemaphore Lab信号量实验室4 real runs4 असली runs4 次真实运行
Limit:सीमा:上限:

SectionSection第 13 节

When to Use async / await 🧭async / await कब लें 🧭何时使用 async / await 🧭

✅
Web Servers and APIsWeb Servers और APIsWeb 服务器与 API
fastapi, uvicorn, starlette
FastAPI, Starlette, aiohttp — handle thousands of open connections on one worker. Standard for modern Python web services.
FastAPI, Starlette, aiohttp — एक worker पर हज़ारों खुले connections संभालते हैं। आधुनिक Python web services का मानक।
FastAPI、Starlette、aiohttp — 在一个工作进程上处理成千上万个连接。现代 Python Web 服务的标准做法。
✅
API Aggregation and ScrapingAPI Aggregation और ScrapingAPI 聚合与爬虫
aiohttp, httpx, playwright
Fetch 100 endpoints and combine the results. The difference between 100 s one-by-one and a few seconds together is huge.
100 endpoints fetch करके results जोड़िए। एक-एक करके 100 s और साथ में कुछ seconds का फ़र्क़ बहुत बड़ा है।
获取 100 个接口并合并结果。逐个请求要 100 秒,一起请求只要几秒,差别巨大。
✅
Chat Bots and Real-TimeChat Bots और Real-Time聊天机器人与实时应用
discord.py, websockets
Discord bots, WebSocket clients, MQTT — thousands of long-lived connections that are idle most of the time.
Discord bots, WebSocket clients, MQTT — हज़ारों लंबे connections जो ज़्यादातर समय ख़ाली रहते हैं।
Discord 机器人、WebSocket 客户端、MQTT — 成千上万个长连接,大部分时间都处于空闲。
❌
CPU-Heavy Mathभारी गणना繁重的计算
numpy, pandas, scikit-learn
Image processing, Python loops, ML training — one thread, no parallel gain. Use multiprocessing (or libraries that release the GIL).
Image processing, Python loops, ML training — एक thread, कोई parallel फ़ायदा नहीं। Multiprocessing लीजिए (या GIL छोड़ने वाली libraries)।
图像处理、Python 循环、机器学习训练 — 单线程,没有并行收益。请使用多进程(或会释放 GIL 的库)。
❌
Small Simple Scriptsछोटी सरल Scripts简单的小脚本
one-off automationएक बार की automation一次性自动化
If you fetch 2 URLs once a day, the extra complexity is not worth it. requests is fine.
अगर दिन में एक बार 2 URLs fetch करते हैं, तो अतिरिक्त जटिलता का कोई फ़ायदा नहीं। requests ठीक है।
如果你每天只获取 2 个 URL,额外的复杂度并不值得。用 requests 就好。
❌
Blocking-Library-Heavy CodeBlocking Libraries से भरा Code大量依赖阻塞库的代码
legacy DB drivers, imaging libsपुराने DB drivers, imaging libs老式数据库驱动、图像库
If nearly everything you call is a blocking library, threads may be cleaner than wrapping every call in to_thread().
अगर आप लगभग सब कुछ blocking library से बुलाते हैं, तो हर call को to_thread() में लपेटने से threads ज़्यादा साफ़ हो सकते हैं।
如果你调用的几乎都是阻塞库,用线程可能比把每个调用都包进 to_thread() 更清爽。

SectionSection第 14 节

Modern Pattern — asyncio.TaskGroup (Python 3.11+) 🧩आधुनिक Pattern — asyncio.TaskGroup (Python 3.11+) 🧩现代模式 — asyncio.TaskGroup(Python 3.11+)🧩

TaskGroup is the recommended choice when you want clean error handling: if any child task fails, the group cancels the tasks that are still running and raises the errors together as an ExceptionGroup.

जब आपको साफ़ error handling चाहिए, TaskGroup सुझाया गया विकल्प है: अगर कोई child task fail होता है, तो group अब भी चल रहे tasks को cancel करता है और errors को एक साथ ExceptionGroup के रूप में raise करता है।

当你需要干净的错误处理时,推荐使用 TaskGroup:只要有任何子任务失败,这个组就会取消 仍在运行 的任务,并把错误合并成一个 ExceptionGroup 抛出。

import asyncio, time

T0 = time.perf_counter()
def now(): return f"[{time.perf_counter() - T0:.1f}s]"

async def work(name, delay, fail=False):
    try:
        await asyncio.sleep(delay)
        if fail:
            raise RuntimeError(f"{name} exploded")
        print(now(), f"{name} finished ok")
        return f"{name} ok"
    except asyncio.CancelledError:
        print(now(), f"{name} was cancelled")
        raise

async def main():
    try:
        async with asyncio.TaskGroup() as tg:
            t1 = tg.create_task(work("A", 1))
            t2 = tg.create_task(work("B", 2, fail=True))
            t3 = tg.create_task(work("C", 3))
    except* RuntimeError as eg:              # exception-group syntax
        for e in eg.exceptions:
            print(now(), f"caught: {e}")
    print("A:", t1.result(), "| C cancelled?", t3.cancelled())

asyncio.run(main())
OUTPUT
[1.0s] A finished ok [2.0s] C was cancelled [2.0s] caught: B exploded A: A ok | C cancelled? True
🔧
Fixed: Task A Was Not Cancelled — It Had Already Finishedसुधार: Task A Cancel नहीं हुआ — वह पहले ही ख़त्म हो चुका था已修正:任务 A 没有被取消 — 它早已完成

The original said "tasks A and C were auto-cancelled". We added prints to see exactly what happened: A finished OK at 1 s, B failed at 2 s, and only C (still sleeping until 3 s) was cancelled. t1.result() still gives "A ok". A TaskGroup cancels only the tasks that have not finished yet.

मूल में कहा गया "tasks A और C अपने-आप cancel हुए"। हमने ठीक-ठीक देखने के लिए prints जोड़े: A 1 s पर ठीक से ख़त्म हुआ, B 2 s पर fail हुआ, और सिर्फ़ C (जो 3 s तक sleep कर रहा था) cancel हुआ। t1.result() अब भी "A ok" देता है। TaskGroup सिर्फ़ उन tasks को cancel करता है जो अभी ख़त्म नहीं हुए।

原文说"任务 A 和 C 都被自动取消了"。我们加了打印语句来看清到底发生了什么:A 在 1 秒时已正常完成,B 在 2 秒时失败,只有 C(原本要睡到 3 秒)被取消了。t1.result() 仍然返回 "A ok"。TaskGroup 只会取消尚未完成的任务。

💥 Simulator: Failure Lab💥 Simulator: Failure Lab💥 模拟器:失败实验室

A finishes at 1 s, B fails at 2 s, C would finish at 3 s. What happens to the others? Three real runs. (main then waits 1.5 s more so you can see what keeps running.)

A 1 s पर ख़त्म होता है, B 2 s पर fail होता है, C 3 s पर ख़त्म होता। बाक़ियों का क्या होता है? तीन असली runs। (main फिर 1.5 s और रुकता है ताकि आप देख सकें कि क्या चलता रहता है।)

A 在 1 秒完成,B 在 2 秒失败,C 本应在 3 秒完成。其他任务会怎样?三次真实运行。(之后 main 再等 1.5 秒,让你看到哪些任务还在继续运行。)

Failure LabFailure Lab失败实验室3 real runs3 असली runs3 次真实运行

SectionSection第 15 节

Bug Doctor, Golden Rules and Quiz 🏆Bug Doctor, सुनहरे नियम और Quiz 🏆Bug 医生、黄金法则与小测验 🏆

🩺 Simulator: Bug Doctor🩺 Simulator: Bug Doctor🩺 模拟器:Bug 医生

Six real async mistakes. Buggy code on the left, fixed code on the right, changed lines marked. Every output is from a real run (programs that hang were stopped after 4 seconds).

छह असली async ग़लतियाँ। बाईं ओर buggy code, दाईं ओर ठीक किया हुआ, बदली lines चिह्नित। हर output असली run से है (अटकने वाले programs 4 seconds बाद रोके गए)।

六个真实的异步错误。左边是有 bug 的代码,右边是修正后的代码,改动的行已标出。每个输出都来自真实运行(会挂起的程序在 4 秒后被停止)。

Bug DoctorBug DoctorBug 医生6 bugs · real outputs6 bugs · असली outputs6 个 bug · 真实输出
❌ BuggyBuggy有 bug
✅ Fixedठीक किया已修正
⚡ async / await — Rules to Rememberasync / await — याद रखने वाले नियमasync / await — 必须记住的规则
1
Start your program with asyncio.run(main()) at the top level. Never call it inside a coroutine — there, just await.
अपना program top level पर asyncio.run(main()) से शुरू कीजिए। इसे कभी coroutine के अंदर मत बुलाइए — वहाँ बस await कीजिए।
在顶层用 asyncio.run(main()) 启动程序。绝不要在协程内部调用它 — 在那里直接 await。
2
Never call blocking code inside async def: no time.sleep, no requests.get, no long pandas jobs. If you must, wrap it: await asyncio.to_thread(blocking_fn, args).
async def के अंदर कभी blocking code मत बुलाइए: न time.sleep, न requests.get, न लंबे pandas jobs। ज़रूरी हो तो लपेटिए: await asyncio.to_thread(blocking_fn, args)।
绝不要在 async def 中调用阻塞代码:不要 time.sleep、不要 requests.get、不要耗时的 pandas 任务。必须调用时就包装起来:await asyncio.to_thread(blocking_fn, args)。
3
Use asyncio.gather() to run known coroutines together, asyncio.create_task() to start now and collect later, and asyncio.TaskGroup (3.11+) for clean error handling.
जाने-पहचाने coroutines साथ चलाने के लिए asyncio.gather(), अभी शुरू करके बाद में लेने के लिए asyncio.create_task(), और साफ़ error handling के लिए asyncio.TaskGroup (3.11+) लीजिए।
一起运行已知的协程用 asyncio.gather(),现在启动、稍后取结果用 asyncio.create_task(),需要干净的错误处理用 asyncio.TaskGroup(3.11+)。
4
Reuse HTTP sessions — one aiohttp.ClientSession (or httpx.AsyncClient) for many requests, not one per request.
HTTP sessions दोबारा इस्तेमाल कीजिए — कई requests के लिए एक aiohttp.ClientSession (या httpx.AsyncClient), हर request के लिए एक नहीं।
复用 HTTP 会话 — 多个请求共用一个 aiohttp.ClientSession(或 httpx.AsyncClient),而不是每个请求新建一个。
5
Always put a timeout on outside calls. A hung connection without a timeout is a coroutine that never ends. Use asyncio.timeout(seconds).
बाहरी calls पर हमेशा timeout लगाइए। Timeout के बिना अटका connection कभी ख़त्म न होने वाला coroutine है। asyncio.timeout(seconds) लीजिए।
对外部调用总要设置超时。没有超时的挂起连接就是一个永远不会结束的协程。使用 asyncio.timeout(seconds)。
6
When starting thousands of tasks, limit them with a Semaphore (for example 20–50 requests in flight).
हज़ारों tasks शुरू करते समय उन्हें Semaphore से सीमित कीजिए (जैसे एक साथ 20–50 requests)।
启动成千上万个任务时,用 Semaphore 加以限制(例如同时 20–50 个请求)。
7
Keep references to the tasks you create with create_task() and await them. Otherwise a task can disappear mid-run and its errors are only printed at exit as "Task exception was never retrieved".
create_task() से बनाए tasks के references रखिए और उन्हें await कीजिए। वरना task बीच में ग़ायब हो सकता है और उसके errors exit पर सिर्फ़ "Task exception was never retrieved" के रूप में print होते हैं।
保留用 create_task() 创建的任务的引用,并 await 它们。否则任务可能在运行中途消失,它的错误只会在退出时以"Task exception was never retrieved"的形式打印出来。
8
When you catch CancelledError to clean up, always raise it again. Swallowing it means the task can never be stopped.
सफ़ाई के लिए CancelledError पकड़ें तो हमेशा उसे फिर raise कीजिए। उसे निगलने का मतलब है task कभी रोका नहीं जा सकता।
为了清理而捕获 CancelledError 时,一定要重新 raise。吞掉它意味着任务永远停不下来。
9
async is for I/O-bound work. For CPU-heavy Python (math loops, image processing, training), use multiprocessing or ProcessPoolExecutor — async is still one thread.
async I/O-bound काम के लिए है। भारी CPU वाले Python (गणित के loops, image processing, training) के लिए multiprocessing या ProcessPoolExecutor लीजिए — async अब भी एक thread है।
异步适用于 I/O 密集型工作。对于 CPU 繁重的 Python(数学循环、图像处理、训练),请使用多进程或 ProcessPoolExecutor — 异步终究只有一个线程。

Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题

🏆 QuizPick an answer to see why.जवाब चुनें और कारण देखें।选一个答案,看看原因。
🎯
Key Takeawayमुख्य सीख关键要点

async / await lets one thread juggle many waiting jobs: a coroutine pauses at each await, and the event loop runs something else until the wait is over. Use gather(), tasks or a TaskGroup to overlap waits, never block the loop, put timeouts on outside calls, limit concurrency with a Semaphore, and keep CPU-heavy work in processes.

async / await एक thread को कई इंतज़ार करते कामों को संभालने देता है: coroutine हर await पर रुकता है, और event loop इंतज़ार ख़त्म होने तक कुछ और चलाता है। इंतज़ारों को साथ-साथ चलाने के लिए gather(), tasks या TaskGroup लीजिए, loop को कभी block मत कीजिए, बाहरी calls पर timeouts लगाइए, Semaphore से concurrency सीमित कीजिए, और भारी CPU काम processes में रखिए।

async / await 让一个线程同时照看许多等待中的工作:协程在每个 await 处暂停,事件循环在等待结束前去运行别的东西。用 gather()、任务或 TaskGroup 让等待重叠,绝不阻塞事件循环,给外部调用加超时,用 Semaphore 限制并发,把 CPU 繁重的工作交给进程。

🐍 Try Python Yourself

Write, run and visualize Python step by step. No install needed.

You have completed Async Await. View all sections →