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

在GTK4事件循环中添加超时接收蓝牙设备的问题排查

问题排查:Rust+GTK4+bluer异步蓝牙设备枚举无响应

问题描述

使用Rust结合GTK4开发自定义蓝牙客户端时,遇到异步蓝牙设备枚举任务完全未执行的问题:

  • 基于bluer库的设备枚举逻辑在独立async main程序中运行正常,但移入GTK项目后,无法向Arc<Mutex<Vec<String>>>共享队列推送设备名称。
  • 主线程通过glib::timeout_add_local每5秒尝试读取队列,但始终无数据,断点调试发现异步任务根本未启动。

原问题代码

pub fn build_ui(app: Application) -> Application {
    app.connect_activate(move |app| {
        let mut queue = Arc::new(Mutex::new(Vec::<String>::new()));
        let mlayout = Grid::builder()
            .height_request(700)
            .width_request(200)
            .build();
        let text_buf = TextBuffer::new(None);
        let text_view = TextView::builder()
            .buffer(&text_buf)
            .width_request(200)
            .height_request(600)
            .vexpand(true)
            .build();
        let mwindow = ApplicationWindow::builder()
            .application(app)
            .resizable(false)
            .width_request(200)
            .height_request(700)
            .child(&mlayout)
            .build();
        let dev_list = Frame::builder()
            .height_request(700)
            .width_request(200)
            .vexpand(true)
            .child(&text_view)
            .build();
        let title = Label::builder()
            .label("Device List")
            .css_name("Title")
            .width_request(200)
            .height_request(50)
            .justify(gtk4::Justification::Center)
            .build();
        mlayout.attach(&title, 0, 0, 200, 50);
        mlayout.attach(&dev_list, 0, 50, 200, 1);
        mwindow.init_layer_shell();
        mwindow.set_anchor(Edge::Right, true);
        mwindow.set_anchor(Edge::Top, true);
        mwindow.present();
        let txqueue = Arc::clone(&queue);
        thread::spawn(move || async {
            blue_init(txqueue).await;
        });
        gtk4::glib::timeout_add_local(Duration::from_secs(5), move || {
            match queue.clone().try_lock() {
                Ok(dev) => {
                    println!("{:#?}", dev);
                    drop(dev);
                    ControlFlow::Continue
                }
                _ => ControlFlow::Continue,
            }
        });
    });

    app
}

pub async fn blue_init(queue: Arc<Mutex<Vec<String>>>) {
    let runtime = tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap();
    runtime.spawn(async move {
        let session = Session::new().await.unwrap();
        let adapter = session.default_adapter().await.unwrap();
        let discover = adapter.discover_devices().await.unwrap();
        pin_mut!(discover);
        while let Some(evt) = discover.next().await {
            match evt {
                AdapterEvent::DeviceAdded(addr) => {
                    let device = adapter.device(addr).unwrap();
                    let name = device.alias().await.unwrap();
                    match queue.try_lock() {
                        Ok(mut data) => {
                            data.push(name);
                            drop(data);
                        }
                        _ => {}
                    }
                }
                _ => {}
            }
        }
    });
}

问题根源与修复方案

1. 异步任务未被驱动执行

原代码中用std::thread::spawn启动了一个返回Future的闭包,但普通线程不会自动驱动Future执行,导致blue_init(txqueue).await完全没运行。

修复方式:
使用Tokio的spawn(需确保全局已初始化Tokio Runtime),或在普通线程中用block_on阻塞运行Future:

// 方案1:使用全局Tokio Runtime
let txqueue = Arc::clone(&queue);
tokio::spawn(async move {
    blue_init(txqueue).await;
});

// 方案2:在独立线程中创建并阻塞Runtime
let txqueue = Arc::clone(&queue);
std::thread::spawn(move || {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(blue_init(txqueue));
});

2. 嵌套Tokio Runtime导致任务提前退出

blue_init内部再次创建了Tokio Runtime,且创建后仅spawn任务就直接返回,Runtime会在函数结束时立即销毁,内部的设备发现任务根本没机会执行。

修复方式:
移除内部Runtime创建,直接在传入的异步上下文中执行任务:

pub async fn blue_init(queue: Arc<Mutex<Vec<String>>>) {
    let session = Session::new().await.unwrap();
    let adapter = session.default_adapter().await.unwrap();
    let discover = adapter.discover_devices().await.unwrap();
    pin_mut!(discover);
    while let Some(evt) = discover.next().await {
        match evt {
            AdapterEvent::DeviceAdded(addr) => {
                let device = adapter.device(addr).unwrap();
                let name = device.alias().await.unwrap();
                // 改用lock()阻塞等待锁,避免丢失设备数据
                if let Ok(mut data) = queue.lock() {
                    data.push(name);
                }
            }
            _ => {}
        }
    }
}

3. Mutex锁定逻辑优化

原代码使用try_lock会在锁被占用时直接放弃,可能丢失设备信息;同时主线程中克隆Arc再锁定属于冗余操作。

修复主线程超时回调:

gtk4::glib::timeout_add_local(Duration::from_secs(5), move || {
    // 直接锁定原Arc,使用unwrap或优雅处理错误
    if let Ok(dev) = queue.lock() {
        println!("{:#?}", dev);
    }
    ControlFlow::Continue
});

额外注意事项

  • 确保项目依赖中已正确引入Tokio(需启用rt-multi-thread等必要特性)。
  • GTK的UI操作必须在主线程执行,如果后续需要将设备列表更新到TextView,需用glib::idle_add_local将UI操作调度到主线程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 06:54:54