You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MicroPython中如何确保异步任务可靠关闭?

MicroPython异步Web服务器终止问题解决方案

这是MicroPython的Bug吗?

不是。这是MicroPython为资源受限设备优化而设计的asyncio实现差异:

  • CPython中,任务取消时CancelledError会自动传播到协程;
  • MicroPython仅允许顶层asyncio.run()函数捕获异常,协程本身不会主动响应取消,除非在协程内部执行await操作(如asyncio.sleep()、I/O操作等),此时才会触发CancelledError的抛出。

如何让协程处理CancelledError?

在协程内部通过try/except包裹包含await的循环或操作,确保取消信号能被捕获。核心是让协程有机会执行await,触发异常检查:

import asyncio

async def my_coroutine():
    try:
        while True:
            # 执行业务逻辑
            await asyncio.sleep(0)  # 关键:通过await触发取消检查
    except asyncio.CancelledError:
        # 执行清理逻辑(如关闭连接、释放资源)
        print("协程已取消,执行清理")
        raise  # 可选:重新抛出异常让外层处理

关闭microdot/asyncio服务器的有效方法

方法1:捕获KeyboardInterrupt并手动取消服务器任务

针对microdot,保存start_server返回的任务,在顶层捕获中断信号后取消任务并等待清理完成:

import asyncio
from microdot import Microdot

app = Microdot()

@app.route('/')
async def index(request):
    return 'Hello, World!'

async def main():
    # 启动服务器并保存任务对象
    server_task = await app.start_server(port=8000)
    try:
        await server_task  # 阻塞等待服务器运行
    except asyncio.CancelledError:
        # 调用microdot内置的关闭方法
        await app.shutdown()

if __name__ == '__main__':
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        # 顶层捕获中断,run()会自动取消main任务,触发内部的CancelledError处理
        pass

方法2:使用SO_REUSEADDR避免端口占用

如果只是想解决EADDRINUSE问题,可以在启动服务器时设置reuse_address=True(MicroPython的asyncio.start_server和microdot的start_server均支持该参数),允许端口快速复用:

# microdot示例
await app.start_server(port=8000, reuse_address=True)

# 原生asyncio示例
server = await asyncio.start_server(handler, '0.0.0.0', 8000, reuse_address=True)

方法3:ESP32平台的特殊处理

ESP32上无法直接捕获Unix信号,可通过定时器检测中断状态,或在顶层循环中处理:

import asyncio
from microdot import Microdot

app = Microdot()
server_task = None

@app.route('/')
async def hello(request):
    return 'Hello'

async def run_server():
    global server_task
    server_task = await app.start_server(port=8000)
    while True:
        await asyncio.sleep(1)

if __name__ == '__main__':
    try:
        asyncio.run(run_server())
    except KeyboardInterrupt:
        if server_task:
            server_task.cancel()
            # 等待服务器清理完成
            asyncio.run(asyncio.wait([server_task]))
        await app.shutdown()

内容的提问来源于stack exchange,提问作者frans

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 10:35:14