The Story That Explains async / await ☕async / await समझाने वाली कहानी ☕一个讲清 async / await 的故事 ☕
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 लिया → espresso शुरू → जब वह चल रहा है, अगला order लिया, दूसरा shot शुरू किया, order #1 का दूध गर्म किया, order #2 का shot देखा। Barista कभी ख़ाली नहीं खड़ा। वही एक इंसान, वही दो हाथ — पर हर घंटे कहीं ज़्यादा drinks, क्योंकि वह कभी machine का इंतज़ार नहीं करता।
वह दूसरा barista async / await है। Python को कोई extra threads या cores नहीं मिलते — वह बस धीमी चीज़ों (network, disk, timers) का इंतज़ार करते हुए खड़ा रहना बंद कर देता है।
再想象一位更聪明的咖啡师。接单 → 开始萃取 → 趁机器运转时,接下一单,开始另一杯萃取,给 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 处干净地暂停,并在等待期间让其他工作运行。它运行在 一个线程 上,是 协作式 的,对于大部分时间都在等待网络、数据库、文件或定时器的程序来说要快得多。
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 工作变快。它让 等待 变得更划算 — 在等待期间去做别的事。当程序的时间花在等待外部事物上时使用它,而不是花在计算上时。
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 运行。
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。
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 秒。同步代码在每次等待时都会让整个程序停下来。
| Stepक़दम步骤 | Timeसमय时间 |
|---|---|
| Fetch URL 1URL 1 fetch获取 URL 1 | 0.0 s → 1.0 s |
| Fetch URL 2URL 2 fetch获取 URL 2 | 1.0 s → 2.0 s |
| Fetch URL 3URL 3 fetch获取 URL 3 | 2.0 s → 3.0 s |
| Totalकुल总计 | 3.0 s |
| Stepक़दम步骤 | Timeसमय时间 |
|---|---|
| Start URL 1URL 1 शुरू启动 URL 1 | 0.0 s (returns at once)0.0 s (तुरंत लौटता है)0.0 秒(立即返回) |
| Start URL 2URL 2 शुरू启动 URL 2 | 0.0 s (returns at once)0.0 s (तुरंत लौटता है)0.0 秒(立即返回) |
| Start URL 3URL 3 शुरू启动 URL 3 | 0.0 s (returns at once)0.0 s (तुरंत लौटता है)0.0 秒(立即返回) |
| All finishedसब ख़त्म全部完成 | ~1.0 s |
Async starts all three, and the waits overlap: about 1 s instead of 3 s.
Async तीनों शुरू करता है, और इंतज़ार साथ-साथ होते हैं: 3 s की जगह लगभग 1 s।
异步同时启动三个,等待相互重叠:约 1 秒而不是 3 秒。
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 倍以上 — 能重叠的等待越多,加速越大。
The Three Building Blocks 🧱तीन बुनियादी हिस्से 🧱三个基本组成部分 🧱
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 定义的函数。调用它 不会 运行它 — 它返回一个 协程对象,一个暂停的、可恢复的计算。在事件循环运行它之前,什么都不会发生。await x means: "pause me and give control back to the event loop until x is done." Only allowed inside async def.await x का मतलब है: "मुझे रोको और x पूरा होने तक control event loop को वापस दो।" सिर्फ़ async def के अंदर चलता है।await x 的意思是:"暂停我,把控制权交还给事件循环,直到 x 完成。"只能在 async def 中使用。asyncio.run().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")
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() 启动事件循环,问候才真正运行。
Each await suspends the coroutine; the loop resumes it later.
हर await coroutine को रोकता है; loop बाद में उसे फिर चलाता है।
每个 await 都会挂起协程,之后由事件循环恢复。
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 完成,然后把它的结果给我。"在这个协程暂停期间,事件循环可以自由地运行其他任务。由此可以得出四个事实。
Pause, let others run, resume with the result.
रुको, दूसरों को चलने दो, result के साथ फिर चलो।
暂停,让别人运行,带着结果恢复。
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 它们。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 就能用。await runs straight through and no other task runs. await asyncio.sleep(0) is the explicit "let others run now".await सीधे चल जाता है और कोई दूसरा task नहीं चलता। await asyncio.sleep(0) साफ़-साफ़ "अब दूसरों को चलने दो" है।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")
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")
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())
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))
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 之前就已在运行 |
| FutureFutureFuture | low-level; made by the loop or librarieslow-level; loop या libraries बनाती हैं底层对象;由事件循环或库创建 | a box that is filled with a result laterएक डिब्बा जो बाद में result से भरता है一个稍后会被填入结果的"盒子" |
| Anything elseबाक़ी कुछ भी其他任何东西 | await 5 | TypeError — 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)
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。
Inside the Event Loop 🔄Event Loop के अंदर 🔄事件循环内部 🔄
One coroutine runs; the others are suspended until their I/O or timer is ready.
एक coroutine चलता है; बाक़ी अपने I/O या timer के तैयार होने तक रुके रहते हैं।
一个协程在运行;其他协程挂起,直到它们的 I/O 或定时器就绪。
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(),然后启动循环。await that has to wait. Nothing interrupts a coroutine in the middle of plain code.await तक न पहुँचे जिसे इंतज़ार करना हो। सादे code के बीच में coroutine को कुछ नहीं रोकता।await。普通代码执行到一半时,协程不会被打断。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 核对过 — 按 🎲 生成一个新程序。
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 争用,协程也永远不会在普通代码中途被打断。这让异步比线程容易理解得多。
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,或者对它们加以保护。
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())
Each task runs briefly, hits await, and waits. All three waits overlap; then each finishes.
हर task थोड़ा चलता है, await पर पहुँचता है, और इंतज़ार करता है। तीनों इंतज़ार साथ-साथ होते हैं; फिर हर एक ख़त्म होता है।
每个任务短暂运行,遇到 await 后开始等待。三个等待相互重叠,然后各自完成。
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()。真实运行。
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 = 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 时,它就开始运行。await.await पर बारी लेता है।await 处轮流执行。result = await task — waits until the task is done and gives its return value.result = await task — task पूरा होने तक इंतज़ार करता है और उसकी return value देता है।result = await task — 等待任务完成并取得它的返回值。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())
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 秒 — 原文只是这样声称而已。
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 秒。
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}")
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 也是同样的规则。
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())
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())
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 节)展示了如果吞掉这个错误会发生什么。
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 भूलना忘记 await | The coroutine never runs; Python only prints a RuntimeWarningCoroutine कभी नहीं चलता; Python बस एक RuntimeWarning print करता है协程从不运行;Python 只打印一条 RuntimeWarning | Await 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 loop | Inside 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 它们 |
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())
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())
import asyncio
async def helper():
return 42
async def main():
print(asyncio.run(helper())) # asyncio.run() INSIDE a running loop
asyncio.run(main())
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())
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 时间。
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 工作,请改用进程池。
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 都能让一个程序处理许多等待中的任务。远看相似,但工作方式截然不同。关键区别在于 由谁决定何时切换。
| 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 तरीका नहीं没有内置办法 |
| 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() |
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()}")
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
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)))
🧮 Simulator: Lost Update Lab🧮 Simulator: Lost Update Lab🧮 模拟器:丢失更新实验室
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")
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 / await | I/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 — सैकड़ों से हज़ारों每个一个操作系统线程 — 数百到数千 |
| Multiprocessing | CPU-bound (math, ML, images)CPU-bound (गणित, ML, images)CPU 密集(数学、机器学习、图像) | True parallel — separate processesअसली parallel — अलग processes真正并行 — 独立进程 | Heavy — usually one per CPU coreभारी — आम तौर पर हर CPU core पर एक开销大 — 通常每个 CPU 核心一个 |
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()。
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())
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"] 处崩溃。
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")
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 秒。
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 秒的任务,设置不同的限制。每一行是信号量的一个"名额"。真实运行。
When to Use async / await 🧭async / await कब लें 🧭何时使用 async / await 🧭
requests is fine.requests ठीक है।requests 就好。to_thread().to_thread() में लपेटने से threads ज़्यादा साफ़ हो सकते हैं।to_thread() 更清爽。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())
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 秒,让你看到哪些任务还在继续运行。)
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 秒后被停止)。
asyncio.run(main()) at the top level. Never call it inside a coroutine — there, just await.asyncio.run(main()) से शुरू कीजिए। इसे कभी coroutine के अंदर मत बुलाइए — वहाँ बस await कीजिए।asyncio.run(main()) 启动程序。绝不要在协程内部调用它 — 在那里直接 await。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)。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.asyncio.gather(), अभी शुरू करके बाद में लेने के लिए asyncio.create_task(), और साफ़ error handling के लिए asyncio.TaskGroup (3.11+) लीजिए।asyncio.gather(),现在启动、稍后取结果用 asyncio.create_task(),需要干净的错误处理用 asyncio.TaskGroup(3.11+)。aiohttp.ClientSession (or httpx.AsyncClient) for many requests, not one per request.aiohttp.ClientSession (या httpx.AsyncClient), हर request के लिए एक नहीं।aiohttp.ClientSession(或 httpx.AsyncClient),而不是每个请求新建一个。asyncio.timeout(seconds).asyncio.timeout(seconds) लीजिए।asyncio.timeout(seconds)。Semaphore (for example 20–50 requests in flight).Semaphore से सीमित कीजिए (जैसे एक साथ 20–50 requests)।Semaphore 加以限制(例如同时 20–50 个请求)。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"的形式打印出来。CancelledError to clean up, always raise it again. Swallowing it means the task can never be stopped.CancelledError पकड़ें तो हमेशा उसे फिर raise कीजिए। उसे निगलने का मतलब है task कभी रोका नहीं जा सकता।CancelledError 时,一定要重新 raise。吞掉它意味着任务永远停不下来。ProcessPoolExecutor — async is still one thread.ProcessPoolExecutor लीजिए — async अब भी एक thread है।ProcessPoolExecutor — 异步终究只有一个线程。Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题
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 繁重的工作交给进程。