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
相关产品推荐
相关产品推荐

