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

为何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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 13:13:10