Python 异步编程学习手册
本手册面向初学者和进阶者,介绍 Python 标准库中的异步编程相关知识点。内容涵盖同步与异步的区别、async/await 语法、协程、事件循环、任务与并发、Future 对象、控制结构、异步 I/O、异步上下文管理器与迭代器、异常与任务取消、并发原语(锁、信号量、队列等)、常用工具函数(asyncio.run、asyncio.sleep、asyncio.to_thread、loop.run_in_executor)、线程/进程池交互,以及性能注意事项。每节均附有说明和示例代码,帮助读者深入理解。
同步 vs 异步 简介
-
同步 (Synchronous):一个操作 A 必须等待前一个操作 B 完成后才能开始,也就是按顺序执行。例如:
pythonimport time def task_A(): print("Task A 开始") time.sleep(2) # 模拟耗时操作(阻塞) print("Task A 完成") def task_B(): print("Task B 开始") time.sleep(1) print("Task B 完成") task_A() # 执行完后才开始 task_B task_B()在同步模型中,
task_B只有在task_A完全结束后才开始执行[0]。整个程序执行过程中若遇到阻塞(如time.sleep、I/O 操作等),就会等待完成后才继续。 -
异步 (Asynchronous):操作 A 不需要等待操作 B 完成,可以同时进行,或者在等待 B 的过程中执行其他任务。例如:
pythonimport asyncio async def task_A(): print("Task A 开始") await asyncio.sleep(2) # 异步等待,不阻塞事件循环 print("Task A 完成") async def task_B(): print("Task B 开始") await asyncio.sleep(1) print("Task B 完成") async def main(): # 同时启动两个协程,让它们异步运行 await asyncio.gather(task_A(), task_B()) asyncio.run(main())在上述异步示例中,
task_A和task_B可以并发运行,互不等待[1]。事件循环负责在它们执行await时切换,从而提高效率。异步编程强调 非阻塞调用 :在发出调用后,调用方不会一直等待,而是可以继续执行其他代码;而当结果可用时,通过回调、事件或await机制得到通知[2][3]。
async/await 基础语法
-
使用
async def定义异步函数(协程函数),在函数内部可以使用await来暂停等待一个可等待对象(awaitable)。async def和await是 Python 标准库asyncio的核心关键字[4]。 -
协程定义与调用 :
async def声明函数为协程函数。调用协程函数时会返回一个 协程对象 ,但不会立即执行,必须通过await或在事件循环中调度才能运行[5]。例如:pythonimport asyncio async def hello(): print("Hello") await asyncio.sleep(1) print("World") # 单个协程执行示例 asyncio.run(hello())运行该代码,输出将是
Hello、然后等待 1 秒再输出World[4]。注意:直接调用hello()不会执行协程,只返回一个协程对象;只有通过asyncio.run或await hello()才真正执行。 -
await表达式 :await用于挂起当前协程并等待可等待对象完成,通常是另一个协程、asyncio.Future或asyncio.Task[6]。例如:pythonasync def say_after(delay, msg): await asyncio.sleep(delay) print(msg) async def main(): print("开始等待") await say_after(1, "异步消息") print("完成等待") asyncio.run(main())在
main中,await say_after(1, ...)会先打印"开始等待",然后等待 1 秒后打印"异步消息",最后再打印"完成等待"[7]。 -
重要注意事项 :只能在
async def定义的协程内部使用await;在常规函数或全局作用域使用await会导致SyntaxError[8]。同时,如果协程函数内部不含有任何await、return或yield,它仍然是一个协程函数,但通常会立即执行完毕。在异步函数里,可以像同步函数一样抛出异常或返回值,调用者可以通过await捕获异常或获得返回值。
协程(Coroutines)
-
概念 :协程是 Python 中对异步函数的调用结果,是一种可等待(awaitable)的对象。当使用
async def定义的函数被调用时,返回的是协程对象[9]。协程对象必须被await或调度到事件循环中才能执行。 -
协程函数 vs 协程对象:Python 文档将"协程"一词用于两种意义:
- 协程函数:使用
async def定义的函数本身。 - 协程对象:调用协程函数后返回的对象。
例如:
pythonasync def func(): return 42 coro = func() # 此时 func() 返回一个协程对象,但尚未执行只有对
coro进行await或通过事件循环调度时,函数体才会运行并返回值[9]。 - 协程函数:使用
-
示例:
pythonimport asyncio async def nested(): return 42 async def main(): # 如果只是调用 nested() 而不 await,它不会运行: # nested() # 不执行任何操作,只产生一个协程对象[[10]](https://docs.python.org/3/library/asyncio-task.html#:~:text=,RuntimeWarning) # 正确的做法是使用 await: result = await nested() print(result) # 输出 42 asyncio.run(main())在上例中,
await nested()使nested协程真正运行并返回结果 42[10]。
事件循环(Event Loop)
-
作用 :事件循环是异步应用的核心,负责调度和执行所有协程、任务、回调,以及处理网络 I/O 等操作[11]。在一个进程中通常只有一个事件循环(单线程模式),由它来切换运行各个协程。Python 应用开发者一般无需直接操作事件循环,而使用高级函数(如
asyncio.run())启动协程,但了解其概念有助于理解运行机制[11][12]。 -
常用操作 :可以通过
asyncio.get_running_loop()在协程内部获取当前正在运行的事件循环[13],也可以使用asyncio.new_event_loop()创建新的循环、asyncio.set_event_loop()设置当前循环。通常我们使用asyncio.run(main())自动管理事件循环的启动与关闭[7]。 -
示例(手动使用事件循环;高级用法可略):
pythonimport asyncio async def say_hi(): await asyncio.sleep(1) print("Hi from coroutine") # 手动创建并运行事件循环(通常用 asyncio.run 更简单) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: loop.run_until_complete(say_hi()) finally: loop.close()这里
run_until_complete会运行循环直到协程结束。一般情况下,推荐使用asyncio.run启动顶层协程,而不直接操作循环[7]。
任务(Tasks)与并发
-
任务(Task) :
asyncio.Task是对协程的封装,用于将协程调度为独立执行的单元。调用asyncio.create_task(coro)会立即安排协程coro在事件循环中尽快执行,并返回一个Task对象[14]。Task继承自Future,因此可以await等待其完成。示例:pythonimport asyncio async def say(msg, delay): await asyncio.sleep(delay) print(msg) async def main(): # 同时创建两个任务并发执行 task1 = asyncio.create_task(say("Hello", 1)) task2 = asyncio.create_task(say("World", 2)) print("Tasks 已创建") await task1 await task2 print("Tasks 完成") asyncio.run(main())输出类似:
Tasks 已创建 Hello # 大约1秒后 World # 大约2秒后 Tasks 完成 -
并发机制 :事件循环采用协作式调度 ,一次只运行一个任务。当一个
Task在执行中遇到await时,将控制权让回给事件循环,循环可以调度其他任务执行[17]。这与多线程不同:虽然协程看似并发运行,但实际仍在一个线程内顺序执行,只有在遇到 I/O 等待时才切换上下文[18][17]。 -
注意事项 :如果创建了
Task却不保存引用,也不await,那么该任务可能在执行过程中被垃圾回收,无法完成。官方文档建议将需要"后台运行"的任务保存在集合中,并在完成后移除引用,以避免任务在执行前被回收[19]。例如:pythonimport asyncio async def worker(n): await asyncio.sleep(n) print(f"worker {n} done") async def main(): background = set() for i in range(3): task = asyncio.create_task(worker(i)) background.add(task) task.add_done_callback(background.discard) await asyncio.sleep(5) # 等待足够时间让所有后台任务完成 asyncio.run(main())在上例中,
background集合保持了对任务的引用,防止它们在完成前被 GC 回收[19]。
Future 对象
-
概念 :
asyncio.Future是一个底层的可等待对象,用于表示异步操作的最终结果。Future相当于预留一个位置,等待异步操作在未来某个时间"完成"并填入结果。协程可以await一个Future,当Future被设置结果或异常时,协程恢复运行[20]。通常应用代码不需要直接创建Future,而是由事件循环或第三方库返回。例如,loop.run_in_executor()就返回一个Future,供协程等待其结果[20][21]。 -
示例 :下面演示手动创建
Future并在事件循环中完成它(注意,这只是为了示例,实际很少手动做):pythonimport asyncio async def main(): loop = asyncio.get_running_loop() # 创建一个 Future fut = loop.create_future() # 在 1 秒后为 Future 设置结果 loop.call_later(1, fut.set_result, "完成") # 等待 Future 完成 result = await fut print("Future 返回:", result) asyncio.run(main())这里
fut在 1 秒后被设置了结果"完成",此时await fut结束并返回该结果[20]。
asyncio.gather、asyncio.wait、asyncio.shield 等控制结构
-
asyncio.gather:用于并发运行多个 awaitable(协程、任务或 Future),并将它们的结果收集到列表中。默认情况下,gather会在任一 awaitable 抛出异常时将异常向上抛出,而其他 awaitable 继续运行[22]。示例:pythonimport asyncio async def work(n): await asyncio.sleep(n) return n async def main(): results = await asyncio.gather(work(1), work(2), work(3)) print(results) # [1, 2, 3] asyncio.run(main())上例中,三个
work协程并发执行,最终结果为[1, 2, 3]。 -
asyncio.wait:类似于gather,可以并发等待多个任务,但是返回两个集合:完成的 (done) 和未完成的 (pending)[23]。常用return_when参数控制行为,如asyncio.FIRST_COMPLETED表示只要有一个任务完成就返回;asyncio.ALL_COMPLETED(默认)表示等待所有完成;asyncio.FIRST_EXCEPTION表示任何任务出现异常时就返回[24][25]。与gather不同,wait不会取消尚未完成的任务,仅返回状态。示例:pythondone, pending = await asyncio.wait( [asyncio.create_task(work(1)), asyncio.create_task(work(2))], return_when=asyncio.FIRST_COMPLETED ) print("完成的任务数:", len(done)) -
asyncio.shield:用于屏蔽取消 。如果对一个协程使用shield,那么即使包含它的父协程被取消,该协程内部运行也不会被自动取消[26]。例如:pythonimport asyncio async def inner(): try: await asyncio.sleep(5) return "inner 完成" except asyncio.CancelledError: print("inner 收到取消") raise async def main(): task = asyncio.create_task(inner()) try: # 即使 main 被取消,inner 仍会继续运行完成 result = await asyncio.shield(task) except asyncio.CancelledError: print("main 收到取消") else: print("任务返回:", result) # 在 1 秒后取消 main 任务 async def runner(): main_task = asyncio.create_task(main()) await asyncio.sleep(1) main_task.cancel() try: await main_task except: pass asyncio.run(runner())在上述代码中,
await asyncio.shield(task)保证了即使外层main()被取消,inner()也会继续运行直到完成[26]。如果希望完全忽略取消,可以在shield外层捕获CancelledError[27]。 -
超时等待 :
asyncio.wait_for(aw, timeout)等待指定的 awaitable 完成,超时则取消其任务并抛出asyncio.TimeoutError[28]。示例:pythonasync def slow(): await asyncio.sleep(10) async def main(): try: await asyncio.wait_for(slow(), timeout=2) except asyncio.TimeoutError: print("操作超时!") asyncio.run(main())如上,如果
slow()运行超过 2 秒,则抛出超时异常。
异步 I/O 与 asyncio.StreamReader/StreamWriter
-
异步 I/O :
asyncio提供了高层次的网络流(stream)接口,封装了 socket 读写,使编程更简洁。按照官方文档,Streams 是"高层次的 async/await 原语,用于处理网络连接,允许发送和接收数据而无需使用回调或底层协议 "[29]。 -
常用函数 :
asyncio.open_connection(host, port)建立 TCP 连接,返回(StreamReader, StreamWriter)对象对[30]。类似地,asyncio.start_server可启动一个服务器,并在新连接时回调给定的协程,提供(reader, writer)。StreamReader可以用于读取数据,StreamWriter用于写入数据。 -
示例(TCP 回声客户端):
pythonimport asyncio async def tcp_echo_client(msg): reader, writer = await asyncio.open_connection('127.0.0.1', 8888) print(f"发送:{msg!r}") writer.write(msg.encode()) await writer.drain() # 确保数据发送 data = await reader.read(100) print(f"接收:{data.decode()!r}") writer.close() await writer.wait_closed() print("连接已关闭") asyncio.run(tcp_echo_client("Hello, AsyncIO!"))在该示例中,客户端通过
open_connection获取读写流,发送数据后用await writer.drain()等待缓冲区刷新,然后用await reader.read()读取服务器响应,最后关闭连接[31]。
异步上下文管理器 与 异步迭代器
-
异步上下文管理器 :使用
async with语法,可以在异步上下文中自动管理资源。Python 提供了@contextlib.asynccontextmanager装饰器,简化异步上下文管理器的定义。其用法类似于@contextlib.contextmanager,但用于async with语句[32]。例如:pythonfrom contextlib import asynccontextmanager import asyncio @asynccontextmanager async def acquire_resource(): print("获取资源") await asyncio.sleep(0.5) # 模拟异步获取资源 resource = "RESOURCE" try: yield resource finally: print("释放资源") await asyncio.sleep(0.5) # 模拟清理 async def main(): async with acquire_resource() as r: print("使用资源:", r) asyncio.run(main())运行结果会先输出"获取资源",然后在
async with块中输出"使用资源",最后在块退出时自动执行finally中的释放操作[33]。 -
异步迭代器 :使用
async for可以异步迭代一个可迭代对象。常见的模式是定义异步生成器函数:在async def函数中使用yield,并且可以包含await来异步生成序列[34]。例如,一个简单的异步计数生成器:pythonasync def async_range(start, end): for i in range(start, end): await asyncio.sleep(0.3) # 模拟异步获取下一个值 yield i async def main(): async for n in async_range(1, 6): print(n) asyncio.run(main())该示例中,
async_range定义了一个异步生成器,每次yield一个值时异步暂停。使用async for可以依次取出这些值[34],打印 1 到 5。如果需要更复杂的状态,可以自己实现类的异步迭代器,需要定义
.__aiter__()和.__anext__()方法。然而,在实践中,大多数情况下直接使用异步生成器是更便捷的方式[34]。
异常处理 与 任务取消
-
任务取消 :可以对
Task调用.cancel()来取消其执行。当任务被取消时,会在协程内抛出asyncio.CancelledError异常[35]。协程应当使用try/finally或try/except捕获CancelledError以执行必要的清理,并在处理完后重新抛出(因为CancelledError属于BaseException,正常的except Exception捕获不到它)[36]。例如:pythonimport asyncio async def worker(): try: print("工作开始") await asyncio.sleep(5) print("工作完成") finally: print("收到取消,进行清理") async def main(): task = asyncio.create_task(worker()) await asyncio.sleep(1) task.cancel() try: await task except asyncio.CancelledError: print("任务已取消") asyncio.run(main())输出示例:
工作开始 收到取消,进行清理 任务已取消在上例中,
worker在await asyncio.sleep时被取消,并进入finally执行清理操作[36]。 -
超时与异常 :使用
asyncio.wait_for超时会取消任务并抛出TimeoutError。此外,任何协程中抛出的异常都可以像同步代码一样被外层捕获。例如:pythonasync def faulty(): raise ValueError("出现错误") async def main(): try: await faulty() except ValueError as e: print("捕获到异常:", e) asyncio.run(main())可捕获协程内的异常。
asyncio.wait_for举例请参见上节。
信号量、锁、队列 等并发原语
Python 的 asyncio 提供了多种并发原语,类似于 threading 模块,但专为同一个线程内的协程设计:
-
互斥锁
asyncio.Lock:用于在多个协程之间保证一次只有一个能访问共享资源。使用async with lock:比直接await lock.acquire()更简单[37]。示例:pythonimport asyncio lock = asyncio.Lock() async def worker(name): async with lock: print(f"{name} 获得锁,进入临界区") await asyncio.sleep(1) print(f"{name} 离开临界区") async def main(): await asyncio.gather(worker("A"), worker("B")) asyncio.run(main())在该示例中,A、B 两个协程争夺同一个锁,所以它们不会同时进入临界区,而是串行执行[37]。
-
信号量
asyncio.Semaphore:管理一个内部计数器,用来控制同时访问某资源的最大协程数。调用await sem.acquire()时,如果计数器已为 0,则挂起等待;release()会将计数器加 1[38]。async with sem:等价于 acquire/release 组合使用[37]。示例(允许最多 2 个协程同时进入临界区):pythonimport asyncio sem = asyncio.Semaphore(2) async def task(n): async with sem: print(f"任务{n}进入临界区") await asyncio.sleep(1) print(f"任务{n}离开临界区") async def main(): await asyncio.gather(*(task(i) for i in range(5))) asyncio.run(main()) -
队列
asyncio.Queue:异步队列用于在生产者和消费者协程之间传递数据,类似于queue.Queue。队列方法如put()和get()都是异步的,默认队列无限大(maxsize=0),也可指定最大长度[39]。示例:多个任务从队列取数据并处理,直到队列空:pythonimport asyncio, random, time async def worker(name, queue): while True: # 等待队列中的一项 item = await queue.get() # 处理项 print(f"{name} 处理 {item}") await asyncio.sleep(item) # 通知队列工作完成 queue.task_done() async def main(): queue = asyncio.Queue() # 将 5 个随机任务加入队列 for i in range(5): delay = random.uniform(0.1, 1.0) queue.put_nowait(delay) # 启动三个 worker 协程 tasks = [asyncio.create_task(worker(f"worker-{i}", queue)) for i in range(3)] start = time.monotonic() await queue.join() # 等待队列处理完成 elapsed = time.monotonic() - start print(f"总耗时:{elapsed:.2f} 秒") # 取消 worker 协程 for t in tasks: t.cancel() asyncio.run(main())在上述例子中,多个协程并发消费同一个队列,使用
await queue.get()获取任务,处理后调用queue.task_done()通知完成。使用await queue.join()等待所有任务处理完毕[40]。 -
其他原语 :
asyncio.Event、asyncio.Condition、asyncio.Barrier等提供了更复杂的同步机制,使用方式类似于线程同步原语。这些原语都是非线程安全的,仅供同一事件循环中的协程使用。具体用法可参考官方文档。
asyncio.run、asyncio.sleep、asyncio.to_thread、run_in_executor
-
asyncio.run:在最高层启动一个协程,自动管理事件循环的创建、运行和关闭。这是运行异步程序的推荐方式[7]。示例:asyncio.run(main())。 -
asyncio.sleep:异步版本的休眠函数,不阻塞事件循环。与time.sleep()不同,await asyncio.sleep(n)在 n 秒内让出控制权,允许其他协程运行[7]。示例:await asyncio.sleep(1)。 -
asyncio.to_thread:Python 3.9 引入,用于在后台线程中运行阻塞函数,并返回一个可等待对象(Future)。这主要用于避免 I/O 密集型或阻塞调用阻塞事件循环[41][42]。例如将普通的文件操作或计算任务放到线程池:pythonimport asyncio import time def blocking(): print("开始阻塞操作") time.sleep(1) print("阻塞操作完成") async def main(): print("启动异步任务") await asyncio.to_thread(blocking) # 在子线程执行 blocking() print("异步任务结束") asyncio.run(main())输出示例为:先"开始阻塞操作",1 秒后"阻塞操作完成",再输出"异步任务结束",期间事件循环没有被阻塞[43]。
-
loop.run_in_executor:用于在指定的线程池或进程池中运行阻塞函数。必须通过事件循环对象调用(通常通过asyncio.get_running_loop()),并传入一个concurrent.futures.Executor(如ThreadPoolExecutor或ProcessPoolExecutor)或None(表示使用默认线程池)[21]。示例:pythonimport asyncio import concurrent.futures def cpu_bound(x): return sum(i*i for i in range(10**6)) + x async def main(): loop = asyncio.get_running_loop() # 默认线程池 res1 = await loop.run_in_executor(None, cpu_bound, 10) print("线程池结果:", res1) # 自定义线程池 with concurrent.futures.ThreadPoolExecutor() as pool: res2 = await loop.run_in_executor(pool, cpu_bound, 20) print("自定义线程池结果:", res2) # 自定义进程池 with concurrent.futures.ProcessPoolExecutor() as pool: res3 = await loop.run_in_executor(pool, cpu_bound, 30) print("进程池结果:", res3) asyncio.run(main())以上示例中,
cpu_bound为耗时计算函数。run_in_executor将其运行在线程池或进程池中,并异步等待结果[44]。
与线程池/进程池的交互(concurrent.futures)
Python 的标准库 concurrent.futures 提供了线程池(ThreadPoolExecutor)和进程池(ProcessPoolExecutor)。在异步代码中,可通过 loop.run_in_executor() 将阻塞调用提交给这些池。上节示例展示了如何使用默认线程池、指定线程池或进程池来运行函数。Python 3.11 还引入了 InterpreterPoolExecutor,允许在多个 Python 解释器进程中并行运行(适用于释放 GIL 的场景)[45]。
此外,如果从另一个线程想要向事件循环提交协程任务,可以使用 asyncio.run_coroutine_threadsafe(coro, loop)。该函数将协程提交到指定事件循环,并返回一个 concurrent.futures.Future,让调用线程等待结果[46]。通常只有在线程间协调时需要用到。
性能注意事项与常见陷阱
-
I/O 绑定 vs CPU 绑定 :
asyncio和threading等适用于 I/O 密集型任务,而对于 CPU 密集型任务(大量计算)需要谨慎。由于 Python 的全局解释器锁(GIL),同一线程中协程仍是串行执行,无法利用多核并行[18]。因此,CPU 绑定任务最好使用进程池或 C 扩展释放 GIL[47][44]。另一方面,如果任务中包含大量的await(I/O 等待),使用asyncio能显著提升吞吐。 -
避免阻塞事件循环 :任何会阻塞线程的操作(如
time.sleep、同步文件/网络 I/O 等)都不应在协程中直接调用,否则会挂起整个事件循环。应使用异步版本(如await asyncio.sleep、异步文件库)或将其移入线程池(asyncio.to_thread/run_in_executor)[43][21]。 -
任务调度开销:创建大量短小任务可能会有调度开销,短任务的串行执行有时比大量切换效率更高。合理使用并发原语(如限制并发数的信号量、任务组等)可以避免过度并发。
-
取消与清理 :被取消的协程如果在中间层捕获了
CancelledError,可能会导致一些高级并发结构(如asyncio.TaskGroup)无法正常工作[36]。一般应确保在捕获CancelledError后重新抛出。建议在协程中使用try/finally做清理,而不是吞掉取消异常[36]。 -
引用管理 :正如前述,如果不保留对
Task的引用,可能导致任务提前结束或被回收[19]。对于"火并忘"类型的后台任务,务必将其保存到全局集合或使用任务组(asyncio.TaskGroup)管理。 -
调试注意 :异步代码的调试相比同步更加复杂,可考虑开启调试模式(
loop.set_debug(True)或环境变量PYTHONASYNCIODEBUG=1),及时捕捉未处理的异常和资源泄漏。
总之,异步编程并不适合所有场景。在真正需要同时处理大量网络/磁盘 I/O 时,asyncio 可以大幅提升性能;但对于简单的顺序流程或以计算为主的任务,其优势有限[18]。理解其工作机制、避免常见陷阱,才能编写高效、正确的异步代码。
参考引用:
[0] 理解同步异步与阻塞非阻塞------傻傻分不清楚的终极指南-腾讯云开发者社区-腾讯云
[1] 理解同步异步与阻塞非阻塞------傻傻分不清楚的终极指南-腾讯云开发者社区-腾讯云
[2] 理解同步异步与阻塞非阻塞------傻傻分不清楚的终极指南-腾讯云开发者社区-腾讯云
[3] 理解同步异步与阻塞非阻塞------傻傻分不清楚的终极指南-腾讯云开发者社区-腾讯云
[4] Coroutines and Tasks --- Python 3.14.2 documentation
[5] Coroutines and Tasks --- Python 3.14.2 documentation
[6] Coroutines and Tasks --- Python 3.14.2 documentation
[7] Coroutines and Tasks --- Python 3.14.2 documentation
[8] Python's asyncio: A Hands-On Walkthrough -- Real Python
[9] Coroutines and Tasks --- Python 3.14.2 documentation
[10] Coroutines and Tasks --- Python 3.14.2 documentation
[11] Event Loop --- Python 3.14.2 documentation
[12] Event Loop --- Python 3.14.2 documentation
[13] Event Loop --- Python 3.14.2 documentation
[14] Coroutines and Tasks --- Python 3.14.2 documentation
[15] Coroutines and Tasks --- Python 3.14.2 documentation
[16] Coroutines and Tasks --- Python 3.14.2 documentation
[17] Coroutines and Tasks --- Python 3.14.2 documentation
[18] Python's asyncio: A Hands-On Walkthrough -- Real Python
[19] Coroutines and Tasks --- Python 3.14.2 documentation
[20] Coroutines and Tasks --- Python 3.14.2 documentation
[21] Event Loop --- Python 3.14.2 documentation
[22] Coroutines and Tasks --- Python 3.14.2 documentation
[23] Coroutines and Tasks --- Python 3.14.2 documentation
[24] Coroutines and Tasks --- Python 3.14.2 documentation
[25] Coroutines and Tasks --- Python 3.14.2 documentation
[26] Coroutines and Tasks --- Python 3.14.2 documentation
[27] Coroutines and Tasks --- Python 3.14.2 documentation
[28] Coroutines and Tasks --- Python 3.14.2 documentation
[29] Streams --- Python 3.14.2 documentation
[30] Streams --- Python 3.14.2 documentation
[31] Streams --- Python 3.14.2 documentation
[32] contextlib --- Utilities for with-statement contexts --- Python 3.14.2 documentation
[33] contextlib --- Utilities for with-statement contexts --- Python 3.14.2 documentation
[34] Asynchronous Iterators and Iterables in Python -- Real Python
[35] Coroutines and Tasks --- Python 3.14.2 documentation
[36] Coroutines and Tasks --- Python 3.14.2 documentation
[37] Synchronization Primitives --- Python 3.14.2 documentation
[38] Synchronization Primitives --- Python 3.14.2 documentation
[39] Queues --- Python 3.14.2 documentation
[40] Queues --- Python 3.14.2 documentation
[41] Coroutines and Tasks --- Python 3.14.2 documentation
[42] Coroutines and Tasks --- Python 3.14.2 documentation
[43] Coroutines and Tasks --- Python 3.14.2 documentation
[44] Event Loop --- Python 3.14.2 documentation
[45] Event Loop --- Python 3.14.2 documentation
[46] Coroutines and Tasks --- Python 3.14.2 documentation
[47] Python's asyncio: A Hands-On Walkthrough -- Real Python