如何停止asyncio循环:使用bleak读Arduino BLE数据时Ctrl+C失效问题
问题原因及修复方案
核心问题点
- 你在asyncio协程中使用了同步阻塞的
time.sleep(),会卡住整个事件循环,导致操作系统的Ctrl+C中断信号(SIGINT)无法被事件循环及时处理,这是Ctrl+C失效的核心诱因 shutdown()是普通同步函数,但client.disconnect()是bleak提供的异步方法,你同步调用该方法根本无法正常执行断开逻辑,且用loop.run_until_complete()运行同步函数本身就是错误用法main函数是异步协程,你在if __name__ == "__main__"中直接调用main()属于语法错误,该代码块和上方全局的事件循环执行逻辑也存在冲突- 你全程没有启动GATT特征的notify监听,代码末尾的
await client.stop_notify()属于无效调用 main函数中捕获了所有Exception类型异常,会拦截部分运行时错误导致外层无法感知
修复后可直接运行的代码
import asyncio import struct import numpy as np from datetime import datetime from bleak import BleakClient # 修复了原变量名的拼写错误 LENGHT -> LENGTH BUFFER_LENGTH = 13 PACKET_NUMBER = BUFFER_LENGTH * 2 address = "04:56:14:27:55:E8" MODEL_NBR_UUID = "0000101a-0000-1000-8000-00805f9b34fb" client = None # 提前声明全局变量避免引用报错 def process_data(dati): data = np.array(struct.unpack('H' * BUFFER_LENGTH, dati)) print('_____________DATA_____________') print(data) print('_____________END______________') # 改为异步函数,适配bleak的异步断开逻辑 async def shutdown(): global client if client and client.is_connected: await client.disconnect() print('_____________INTERRUPTED_____________') async def main(address): global client client = BleakClient(address) # 优化重连逻辑,避免连接失败疯狂打日志 while not client.is_connected: try: await client.connect() print("蓝牙设备连接成功") except Exception as e: print(f"连接失败,1秒后重试: {e}") await asyncio.sleep(1) try: # 替换同步sleep为异步sleep,不阻塞事件循环 await asyncio.sleep(1) start_timestamp = datetime.timestamp(datetime.now()) while True: dati = await client.read_gatt_char(MODEL_NBR_UUID) process_data(dati) await asyncio.sleep(2) # 仅捕获业务异常,不拦截中断信号 except Exception as e: print(f"运行出错: {e}") finally: # 无论正常退出还是异常退出都主动断开连接 if client.is_connected: await client.disconnect() if __name__ == "__main__": loop = asyncio.get_event_loop() try: loop.run_until_complete(main(address)) except KeyboardInterrupt: print("Process interrupted") loop.run_until_complete(shutdown()) finally: loop.close()
额外优化建议
- 所有异步开发场景下都不要调用同步阻塞方法,优先使用对应库的异步实现,避免卡住事件循环
- 如果后续需要更高的实时性,可以把主动轮询
read_gatt_char的逻辑改成bleak的notify回调模式,不需要主动定时读,设备有数据更新会主动推送给PC - 尽量减少全局变量的使用,这里为了兼容你原有的写法保留了
client全局变量,更规范的写法可以把client作为参数传递或者封装到类中
内容的提问来源于stack exchange,提问作者valio
相关产品推荐
相关产品推荐

