
本文深入剖析 asyncio.run() 如何阻塞主线程、为何 print("Rest of the method...") 不会立即执行,并详解如何通过线程(threading)或线程池执行器(ThreadPoolExecutor)实现真正的并发,突破事件循环的单线程限制。
本文深入剖析 `asyncio.run()` 如何阻塞主线程、为何 `print("rest of the method...")` 不会立即执行,并详解如何通过线程(`threading`)或线程池执行器(`threadpoolexecutor`)实现真正的并发,突破事件循环的单线程限制。
在 Python 的异步编程中,一个常见误区是认为 await asyncio.sleep() 会让出控制权后,主线程就能“自由”执行后续同步代码——但事实并非如此。关键在于:asyncio.run() 是一个阻塞式调用,它会启动事件循环并 同步等待所有任务完成(或被显式取消),之后才返回。因此,在你的示例中:
asyncio.run(main()) # ← 此处阻塞!直到 main() 中的 gather(t1, t2) 永不结束(因两个 while True 无限循环),该行永不返回
print("Rest of the method is executing....") # ← 这行永远无法执行
尽管 fetcher() 和 monitor() 通过 await 协作式让出控制权,它们始终运行在同一个主线程内的事件循环中,共享 GIL(全局解释器锁)。GIL 并未被打破——异步本身不创建新线程,只是通过事件循环在多个协程间快速切换(cooperative multitasking),实现高 I/O 并发,而非并行。
✅ 正确理解:
- async/await = 单线程内高效调度 I/O 等待任务(非阻塞);
- asyncio.run() = 同步入口,阻塞当前线程直至整个 async 主函数退出;
- run_in_executor() / ThreadPoolExecutor = 借助多线程绕过事件循环,用于 CPU 密集型或需真正并行的场景(此时 GIL 在不同线程间切换,但同一时刻仅一个线程执行 Python 字节码)。
要让 "Rest of the method is executing...." 立即打印并持续运行,必须将 asyncio.run(main()) 移出主线程。以下是两种推荐方案:
✅ 方案一:使用 threading.Thread(清晰可控)
import asyncio
import threading
import time
async def get_some_values_from_io():
print("Getsome value Executing...")
await asyncio.sleep(3)
return [100, 200]
vals = []
async def fetcher():
while True:
print("Fetcher Executing...")
io_vals = await get_some_values_from_io()
vals.extend(io_vals) # ← 修正:原代码 append(io_vals) 会嵌套列表,应为 extend
async def monitor():
while True:
print("Monitor Executing...", f"len(vals) = {len(vals)}")
await asyncio.sleep(3)
async def main():
await asyncio.gather(fetcher(), monitor())
# 启动异步任务于独立线程
def run_async_in_thread():
asyncio.run(main())
thread = threading.Thread(target=run_async_in_thread, daemon=False)
thread.start()
# 主线程立即继续执行
print("Rest of the method is executing....")
# 保持主线程存活(可选:根据实际需求替换为其他逻辑)
try:
while thread.is_alive():
time.sleep(1)
except KeyboardInterrupt:
print("\nExiting...")
⚠️ 注意事项:
- 使用 daemon=False 确保主线程退出前子线程能完成工作(若需守护行为则设为 True);
- vals 是共享状态,多线程访问时如需更高安全性,建议配合 threading.Lock 或改用 asyncio.Queue(在纯 async 场景中更推荐);
- 示例中已修正原逻辑错误:vals.append(io_vals) → vals.extend(io_vals),避免生成 [[100,200], [100,200], ...]。
✅ 方案二:使用 concurrent.futures.ThreadPoolExecutor(更符合标准实践)
import asyncio
from concurrent.futures import ThreadPoolExecutor
import time
# ...(fetcher, monitor, main 定义同上)...
executor = ThreadPoolExecutor(max_workers=1)
future = executor.submit(asyncio.run, main())
print("Rest of the method is executing....")
# 主线程可继续处理其他任务
try:
while future.running():
time.sleep(0.5)
except KeyboardInterrupt:
future.cancel()
finally:
executor.shutdown(wait=False)
此方式更简洁,且 ThreadPoolExecutor 提供了完善的生命周期管理(如 shutdown())、异常传播和资源回收能力,是生产环境推荐做法。
? 总结
- 异步 ≠ 多线程:asyncio 是单线程协作式并发,asyncio.run() 阻塞调用线程;
- 突破阻塞:需显式启用多线程(threading 或 ThreadPoolExecutor),将事件循环置于后台线程;
- GIL 不矛盾:线程池执行器确实启动新线程,GIL 仍存在,但对 I/O-bound 任务影响极小;对 CPU-bound 任务,可考虑 ProcessPoolExecutor 绕过 GIL;
- 通信建议:跨线程共享数据时优先选用线程安全结构(如 queue.Queue、threading.local)或加锁,避免竞态条件。
掌握这一机制,你便能精准设计混合架构:用 asyncio 处理海量网络请求,用线程/进程池处理文件解析、图像处理等耗时操作,真正发挥 Python 并发模型的全部潜力。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











