Python 现代开发学习笔记

(十二)把已有阻塞调用接到异步程序

1. 放进异步函数后仍会阻塞

# 项目文件:demos/blocking_loop.py
# 错误示例:time.sleep 在事件循环所在的线程中睡眠,心跳要等它结束才能推进。
import asyncio
import time


async def blocking(trace):
    trace.append("阻塞开始")
    time.sleep(0.02)
    trace.append("阻塞结束")


async def heartbeat(trace):
    await asyncio.sleep(0)
    trace.append("心跳")


async def main():
    trace = []
    await asyncio.gather(blocking(trace), heartbeat(trace))
    print(trace)


if __name__ == "__main__":
    asyncio.run(main())

输出 ['阻塞开始', '阻塞结束', '心跳']。网络库如果只有阻塞接口,直接在 async def 中调用,也会在相应等待段阻塞循环。

2. 用 to_thread 等待已有同步函数

# 项目文件:demos/to_thread_bridge.py
# 同步函数在线程中等待,事件循环可以继续运行并最终发出释放信号。
import asyncio
from threading import Event


def legacy_read(release):
    release.wait()
    return "同步读取完成"


async def main():
    release = Event()
    task = asyncio.create_task(asyncio.to_thread(legacy_read, release))
    try:
        await asyncio.sleep(0)
        print("事件循环继续运行")
    finally:
        release.set()
    print(await task)


if __name__ == "__main__":
    asyncio.run(main())

输出 事件循环继续运行 和 同步读取完成。to_thread(函数, 实参...) 把普通函数放到线程中执行,交回一个可等待操作;需要并发时再调度为任务。它适合衔接现有阻塞 I/O,不会把普通计算自动变成多核计算。

取消外层等待不会强行杀死线程,所以同步网络调用仍要有自己的连接、读取超时,长期工作还需要自己的结束信号。网络库已经提供异步接口时,可以直接交给事件循环,不必为每个请求再包一层线程。异步开发中的阻塞调用

直接阻塞循环时心跳停住,to_thread把等待移到线程后循环继续推进

笔记正文与全部题解 · Ctrl / ⌘ K