Reputation: 35
I'm running into an issue while writing my own Bluetooth client using Rust and GTK4.
Here's the idea. I have a main event loop that I want to add a timeout to poll for a new Bluetooth device every 5 seconds because GTK4 is only a synchronous library. I wrote an async function using bluer to enumerate Bluetooth devices. This program works fine in it's own program with an async main function. Taking that same function and moving it into a larger project with a basic GTK UI – for some reason – won't push any devices to the message queue.
The message queue in this context being a Arc<Mutex<Vec<String>>>
. Just a standard Mutex
being shared between threads. This also occurs with sync_channels
and normal channels.
The main thread try's to lock the Mutex
, read it, then drop the lock using drop(Mutex)
. The event on the main loop runs once every 5 seconds. The rest of the time our async function locks the mutex and attempts to push a device name to the message queue so that it can be read by the main thread.
Here's the code I have so far.
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);
}
_ => {}
}
}
_ => {}
}
}
});
Currently, the async task doesn't seem to be running at all when initialized from the main thread when using the GTK libraries. Seems like the blue_init() function is called, but when the async task either fails in general or never runs. Even adding a break point to the async task doesn't break at the break point. Any help with this would be very much appreciated.
Upvotes: 0
Views: 65
Reputation: 23424
thread::spawn(move || async {
blue_init(txqueue).await;
});
This does nothing: it starts a new thread that initializes a future but never polls it and returns immediately. So your blue_init
function is never called (try adding a println
in it to see for yourself).
Instead you should remove the async
from the declaration of blue_init
:
pub fn blue_init (queue: Arc<Mutex<Vec<String>>>) {
todo!();
}
and call it directly in the thread:
thread::spawn (move || blue_init (txqueue));
Moreover quoting from the shudown section of the tokio::runtime::Runtime
docs:
Tasks spawned through
Runtime::spawn
keep running until they yield. Then they are dropped. They are not guaranteed to run to completion, but might do so if they do not yield until completion.
In your case, the runtime is dropped immediately after spawning the task, which causes the task to be canceled as soon as it hits an await
. The solution is to use block_on
instead of spawn
:
pub fn blue_init (queue: Arc<Mutex<Vec<String>>>) {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on (async move { todo!(); });
}
Upvotes: 0