MicroPython 有 asyncio,但为精简版,仅含核心调度器与基础原语,不支持 asyncio.run() 等高级接口;需用 run_forever() 启动,显式创建任务,避免阻塞调用,并注意 sleep(0) 卡死、硬件 I/O 异步桥接及 Task 泄漏问题。

MicroPython 有 asyncio 吗?
有,但不是 CPython 那个完整版。MicroPython 的 asyncio 是精简实现,只保留核心调度器和基础原语(async/await、Task、sleep、create_task),不支持 asyncio.run()、gather、wait_for 等高级接口。如果你照搬 CPython 教程里那套写法,大概率会遇到 AttributeError: module 'asyncio' has no attribute 'run'。
实操建议:
- 用
asyncio.get_event_loop().run_forever()启动调度器,不是asyncio.run() - 所有协程必须显式用
asyncio.create_task()注册,否则不会运行 - 避免在协程里调用阻塞函数(如
time.sleep()),改用await asyncio.sleep() - ESP32/ESP8266 上默认事件循环是单核的,不等于“多线程”,只是协程并发
为什么 await asyncio.sleep(0) 有时卡死?
这是 MicroPython asyncio 最容易踩的坑:在无其他待调度任务时,await asyncio.sleep(0) 可能永远不返回,因为调度器没活干就直接停了——它不像 CPython 那样有后台心跳或信号唤醒机制。
常见错误现象:led_blink 协程一闪就停,或串口监听协程收不到后续数据。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 把
sleep(0)换成sleep(0.001)或更大值,确保调度器有明确退出条件 - 确保至少有一个长期存活的任务(比如一个空循环
while True: await asyncio.sleep(1))防止事件循环退出 - 在 ESP32 上,如果启用了 WiFi,
asyncio会自动集成底层事件,此时sleep(0)行为更稳定;纯裸机运行时尤其要小心
如何让 UART 或 I2C 不阻塞 asyncio?
MicroPython 的 uart.read()、i2c.readfrom() 默认是同步阻塞的,直接 await 它们会挂起整个事件循环。你不能靠加 await 就让它变“异步”——得靠硬件中断 + 回调 + 事件队列来桥接。
使用场景:想一边读传感器,一边闪烁 LED,一边发 MQTT 数据。
实操建议:
- UART:启用中断接收(
uart.irq(trigger=UART.RX_ANY, handler=...)),在 handler 中把数据存进asyncio.Queue,主协程用await queue.get()拿 - I2C:多数传感器不支持中断,只能轮询;但要把
sleep放在轮询间隙(await asyncio.sleep(0.1)),别用time.sleep() - 别试图给
machine.I2C或machine.UART加async包装器——底层没有非阻塞 API,硬包只会掩盖问题
Task 对象泄漏导致内存崩了怎么办?
MicroPython 没有自动 GC 掉已完成的 Task,尤其是用 create_task() 动态生成又没显式 cancel() 或等它结束时,Task 对象会一直占着 RAM。ESP32 上跑几小时后 OOM 是典型表现。
性能影响:每个 Task 占约 300–500 字节,几十个未清理就可能吃光可用内存。
实操建议:
- 用
asyncio.all_tasks()打印当前任务列表(调试时加一句print([t for t in asyncio.all_tasks() if not t.done()])) - 对短生命周期任务,用
await task等它结束;对长任务,保存引用并在合适时机调task.cancel() - 避免在循环里无节制
create_task(),比如每秒创建一个新 blink 任务——改用单个任务里控制状态
协程调度本身不复杂,难的是时刻意识到:你写的不是“多线程”,而是单线程里靠主动让出控制权模拟并发;任何一次忘记 await、误用阻塞调用、或漏掉 Task 清理,都会让这个平衡立刻垮掉。



















