为何Windows11中plt.subplots会阻塞BleakClient.connect?
解决Windows下Bleak与Matplotlib交互式绘图冲突导致的连接冻结问题
问题原因
Windows系统中,Matplotlib默认的TkAgg后端会占用主线程的事件循环,而Bleak依赖的asyncio异步操作也需要主线程事件循环支持,两者冲突导致BLE连接操作(client.connect())被阻塞,程序卡住无响应。
解决方案
方案1:切换Matplotlib后端为QtAgg(推荐,保持交互式)
QtAgg后端的事件循环与asyncio兼容性更好,不会阻塞BLE的异步操作。先安装依赖:
pip install pyqt5
修改后的代码示例:
import asyncio import matplotlib # 切换后端为QtAgg matplotlib.use('QtAgg') import matplotlib.pyplot as plt from bleak import BleakClient # 替换为你的设备地址和特征UUID DEVICE_ADDRESS = "XX:XX:XX:XX:XX:XX" TEMP_CHAR_UUID = "0000ffb2-0000-1000-8000-00805f9b34fb" # 初始化图表 fig, axs = plt.subplots(3, 1, sharex=True) temp_data = [[], [], []] time_points = [] async def read_and_plot_temp(client, plot_idx): # 读取温度特征 raw_value = await client.read_gatt_char(TEMP_CHAR_UUID) temperature = float(int.from_bytes(raw_value, byteorder='little')) / 100 # 更新数据与图表 with plt.ion(): temp_data[plot_idx].append(temperature) time_points.append(len(time_points)) axs[plot_idx].clear() axs[plot_idx].plot(time_points, temp_data[plot_idx], marker='o', linestyle='-') axs[plot_idx].set_title(f"Temperature Sensor {plot_idx+1}") axs[plot_idx].set_ylabel("°C") plt.draw() plt.pause(0.1) # 触发图表刷新 print(f"Sensor {plot_idx+1}: {temperature}°C") async def main(): async with BleakClient(DEVICE_ADDRESS) as client: print(f"Connected to {DEVICE_ADDRESS}") # 循环3次,每次间隔5秒 for i in range(3): await read_and_plot_temp(client, i) await asyncio.sleep(5) if __name__ == "__main__": plt.ion() # 开启交互式模式 try: asyncio.run(main()) except KeyboardInterrupt: print("程序终止") finally: plt.ioff() plt.show()
方案2:用线程分离BLE操作与绘图
将BLE的同步操作放在独立线程中,主线程专注处理Matplotlib的绘图更新,避免事件循环冲突:
import threading import time import matplotlib.pyplot as plt from bleak import BleakClient DEVICE_ADDRESS = "XX:XX:XX:XX:XX:XX" TEMP_CHAR_UUID = "0000ffb2-0000-1000-8000-00805f9b34fb" fig, axs = plt.subplots(3, 1, sharex=True) temp_data = [[], [], []] time_points = [] data_lock = threading.Lock() def ble_read_task(): # BLE连接与读取操作放在独立线程 with BleakClient(DEVICE_ADDRESS) as client: print(f"Connected to {DEVICE_ADDRESS}") for i in range(3): raw_value = client.read_gatt_char(TEMP_CHAR_UUID) temperature = float(int.from_bytes(raw_value, byteorder='little')) / 100 # 线程安全更新数据 with data_lock: temp_data[i].append(temperature) time_points.append(len(time_points)) print(f"Sensor {i+1}: {temperature}°C") time.sleep(5) def plot_update_task(): plt.ion() # 等待3次读取完成 while len(temp_data[0]) < 3: with data_lock: # 更新每个子图 for idx in range(3): axs[idx].clear() axs[idx].plot(time_points[:len(temp_data[idx])], temp_data[idx], marker='o') axs[idx].set_title(f"Temperature Sensor {idx+1}") plt.draw() plt.pause(1) plt.ioff() plt.show() if __name__ == "__main__": # 启动BLE读取线程 ble_thread = threading.Thread(target=ble_read_task) ble_thread.start() # 主线程处理绘图 plot_update_task() ble_thread.join()
验证说明
两种方案都能解决Windows下的冻结问题:
- 方案1保持了异步编程的简洁性,适合原本使用asyncio的场景
- 方案2用线程分离逻辑,对不熟悉异步的开发者更友好
内容的提问来源于stack exchange,提问作者waszee
相关产品推荐
相关产品推荐

