Fabian Wunsch
Fabian Wunsch

Reputation: 23

Wait for backend thread to finish after application.run in Rust

I want to wait for a backend thread (Like this but in my case the backend manages a database which I want to close properly before the application actually exits) to finish (e.g. join it) after application.run() has finished.

use gio::prelude::*;
use gtk::prelude::*;
use gtk::{ApplicationWindow, Label};
use std::env::args;
use std::thread;

fn main() {
    let application = gtk::Application::new(
        Some("com.github.gtk-rs.examples.communication_thread"),
        Default::default(),
    )
    .expect("Initialization failed...");

    let (thr, mut receiver) = start_communication_thread();

    application.connect_activate(move |application| {
        build_ui(application, receiver.take().unwrap())
    });
    application.run(&args().collect::<Vec<_>>());
    thr.join();
}

fn build_ui(application: &gtk::Application, receiver: glib::Receiver<String>) {
    let window = ApplicationWindow::new(application);
    let label = Label::new(None);
    window.add(&label);

    spawn_local_handler(label, receiver);
    window.show_all();
}

/// Spawn channel receive task on the main event loop.
fn spawn_local_handler(label: gtk::Label, receiver: glib::Receiver<String>) {
    receiver.attach(None, move |item| {
        label.set_text(&item);
        glib::Continue(true)
    });
}

/// Spawn separate thread to handle communication.
fn start_communication_thread() -> (thread::JoinHandle<()>, Option<glib::Receiver<String>>) {
    let (sender, receiver) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);

    let thr = thread::spawn(move || {
        let mut counter = 0;
        loop {
            let data = format!("Counter = {}!", counter);
            println!("Thread received data: {}", data);
            if sender.send(data).is_err() {
                break
            }
            counter += 1;
            thread::sleep(std::time::Duration::from_millis(100));
        }
    });
    (thr, Some(receiver))
}

As mentioned above, the only error remaining is that application.connect_activate() takes an Fn closure, the current implementation is FnMut.

Upvotes: 0

Views: 597

Answers (1)

Jonas Berlin
Jonas Berlin

Reputation: 3482

The error message is:

error[E0596]: cannot borrow `receiver` as mutable, as it is a captured variable in a `Fn` closure
  --> src/main.rs:17:31
   |
17 |         build_ui(application, receiver.take().unwrap())
   |                               ^^^^^^^^ cannot borrow as mutable

So you cannot use "receiver" mutably, which is necessary for you to take() its contents.

But if you wrap the receiver inside a Cell, then you can access the immutable Cell's contents mutably. So add this line directly after the line with start_communication_thread():

    let receiver = Cell::new(receiver);

There might be some more correct answer as I am only a beginner at Rust, but at least it seems to work.

Please note that this changes the take() call to be called against the Cell instead of Option, whose implementation has the same effect, replacing the Cell's contents with None.

Upvotes: 1

Related Questions