Search code examples
rustegui

egui interaction with background thread


My goal is to have a background thread counting up every second, which can be started and stopped by buttons on the UI. The gui should display the current time. I thought that the communication between the gui and the background thread could be done via channels for the commands, and via a mutex for the number being counted upwards. In this example, I have omitted the communication via channels, and just focus on the mutex.

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release

use eframe::egui;
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let options = eframe::NativeOptions {
        initial_window_size: Some(egui::vec2(320.0, 240.0)),
        fullscreen: false,
        default_theme: eframe::Theme::Light,
        ..Default::default()
    };
    eframe::run_native(
        "My egui App",
        options,
        Box::new(|_cc| Box::new(Timer::default())),
    );
}

struct Timer {
    time: u32,
    counter: Arc<Mutex<i32>>,
}

impl Default for Timer {
    fn default() -> Self {
        let counter = Arc::new(Mutex::new(0));
        let counter_clone = Arc::clone(&counter);

        //spawn looping thread
        let _ = thread::spawn(move || {
            loop {
                let mut num = counter_clone.lock().unwrap();
                *num += 1;
                std::thread::sleep_ms(100);
            }
        });

        Self {
            time: 0,
            counter: counter,
        }
    }
}

impl eframe::App for Timer {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            let mut num = self.counter.lock().unwrap();

            ui.heading(format!("My egui Application {}", num));
        });
    }
}

This compiles and runs, but it is super laggy. Could you help me to understand what I can do better?


Solution

  • In your sleeping loop you hold the lock for the whole duration you're sleeping.

    loop {
        let mut num = counter_clone.lock().unwrap();
        *num += 1;
        std::thread::sleep_ms(100);
    } // counter_clone only gets released here.
    

    You either want to add a manual call to drop after you increase the number:

    loop {
        let mut num = counter_clone.lock().unwrap();
        *num += 1;
        drop(num); // counter_clone now gets released here before we wait.
        std::thread::sleep_ms(100);
    }
    

    Or just lock it in place:

    loop {
        *counter_clone().lock.unwrap() += 1; // counter_clone gets unlocked at the end of this statement.
        std::thread::sleep_ms(100);
    }