I am creating a UI application with gtk-rs. In that application, I have to spawn a thread to continuously communicate with another process. Sometimes, I have to update the UI based on what happens in that thread. But, I'm not sure how to do this because I am not able to hold a reference to any part of the UI across threads.
Here is the code I tried:
use gtk;
fn main() {
let application =
gtk::Application::new(Some("com.github.gtk-rs.examples.basic"), Default::default()).unwrap()
application.connect_activate(|app| {
let ui_model = build_ui(app);
setup(ui_model);
});
application.run(&[]);
}
struct UiModel { main_buffer: gtk::TextBuffer }
fn build_ui(application: >k::Application) -> UiModel {
let glade_src = include_str!("test.glade");
let builder = gtk::Builder::new();
builder
.add_from_string(glade_src)
.expect("Couldn't add from string");
let window: gtk::ApplicationWindow = builder.get_object("window").unwrap();
window.set_application(Some(application));
window.show_all();
let main_text_view: gtk::TextView = builder.get_object("main_text_view")
return UiModel {
main_buffer: main_text_view.get_buffer().unwrap(),
};
}
fn setup(ui: UiModel) {
let child_process = Command::new("sh")
.args(&["-c", "while true; do date; sleep 2; done"])
.stdout(Stdio::piped())
.spawn()
.unwrap();
let incoming = child_process.stdout.unwrap();
std::thread::spawn(move || { // <- This is the part to pay
&BufReader::new(incoming).lines().for_each(|line| { // attention to.
ui.main_buffer.set_text(&line.unwrap()); // I am trying to update the
}); // UI text from another thread.
});
}
But, I get the error:
| std::thread::spawn(move || {
| _____^^^^^^^^^^^^^^^^^^_-
| | |
| | `*mut *mut gtk_sys::_GtkTextBufferPrivate` cannot be sent between threads safely
This makes sense. I can understand that the Gtk widgets aren't thread safe. But then how do I update them? Is there a way to send signals to the UI thread safely? or is there a way to run the .lines().for_each( loop in the same thread in a way that does not block the UI?
Whatever solution I go with will have to be very high performance. I will be sending much more data than in the example and I want a very low latency screen refresh.
Thanks for your help!
Ok, I solved the problem. For anyone in the future, here is the solution.
glib::idle_add(|| {}) lets you run a closure from another thread on the UI thread (thansk #Zan Lynx). This would be enough to solve the thread safety issue, but it's not enough to get around the borrow checker. No GTKObject is safe to send between threads, so another thread can never even hold a reference to it, even if it will never use it. So you need to store the UI references globally on the UI thread and set up a communication channel between threads. Here is what I did step by step:
Create a way to send data between threads that does not involve passing closures. I used std::sync::mpsc for now but another option might be better long-term.
Create some thread-local global storage. Before you ever start the second thread, store your UI references and the receiving end of that communication pipeline globally on the main thread.
Pass the sending end of the channel to the second thread via a closure. Pass the data you want through that sender.
After passing the data through, use glib::idle_add() -- not with a closure but with a static function -- to tell the UI thread to check for a new message in the channel.
In that static function on the UI thread, access your global UI and receiver variables and update the UI.
Thanks to this thread for helping me figure that out. Here is my code:
extern crate gio;
extern crate gtk;
extern crate pango;
use gio::prelude::*;
use gtk::prelude::*;
use std::cell::RefCell;
use std::io::{BufRead, BufReader};
use std::process::{Command, Stdio};
use std::sync::mpsc;
fn main() {
let application =
gtk::Application::new(Some("com.github.gtk-rs.examples.basic"), Default::default())
.unwrap();
application.connect_activate(|app| {
let ui_model = build_ui(app);
setup(ui_model);
});
application.run(&[]);
}
struct UiModel {
main_buffer: gtk::TextBuffer,
}
fn build_ui(application: >k::Application) -> UiModel {
let glade_src = include_str!("test.glade");
let builder = gtk::Builder::new();
builder
.add_from_string(glade_src)
.expect("Couldn't add from string");
let window: gtk::ApplicationWindow = builder.get_object("window").unwrap();
window.set_application(Some(application));
window.show_all();
let main_text_view: gtk::TextView = builder.get_object("main_text_view").unwrap();
return UiModel {
main_buffer: main_text_view.get_buffer().unwrap(),
};
}
fn setup(ui: UiModel) {
let (tx, rx) = mpsc::channel();
GLOBAL.with(|global| {
*global.borrow_mut() = Some((ui, rx));
});
let child_process = Command::new("sh")
.args(&["-c", "while true; do date; sleep 2; done"])
.stdout(Stdio::piped())
.spawn()
.unwrap();
let incoming = child_process.stdout.unwrap();
std::thread::spawn(move || {
&BufReader::new(incoming).lines().for_each(|line| {
let data = line.unwrap();
// send data through channel
tx.send(data).unwrap();
// then tell the UI thread to read from that channel
glib::source::idle_add(|| {
check_for_new_message();
return glib::source::Continue(false);
});
});
});
}
// global variable to store the ui and an input channel
// on the main thread only
thread_local!(
static GLOBAL: RefCell<Option<(UiModel, mpsc::Receiver<String>)>> = RefCell::new(None);
);
// function to check if a new message has been passed through the
// global receiver and, if so, add it to the UI.
fn check_for_new_message() {
GLOBAL.with(|global| {
if let Some((ui, rx)) = &*global.borrow() {
let received: String = rx.recv().unwrap();
ui.main_buffer.set_text(&received);
}
});
}
Related
I am writing a program that pings a set of targets 100 times, and stores each RTT value returned from the ping into a vector, thus giving me a set of RTT values for each target. Say I have n targets, I would like all of the pinging to be done concurrently. The rust code looks like this:
let mut sample_rtts_map = HashMap::new();
for addr in targets.to_vec() {
let mut sampleRTTvalues: Vec<f32> = vec![];
//sample_rtts_map.insert(addr, sampleRTTvalues);
thread::spawn(move || {
while sampleRTTvalues.len() < 100 {
let sampleRTT = ping(addr);
sampleRTTvalues.push(sampleRTT);
// thread::sleep(Duration::from_millis(5000));
}
});
}
The hashmap is used to tell which vector of values belongs to which target. The problem is, how do I retrieve the updated sampleRTTvalues from each thread after the thread is done executing? I would like something like:
let (name, sampleRTTvalues) = thread::spawn(...)
The name, being the name of the thread, and sampleRTTvalues being the vector. However, since I'm creating threads in a for loop, each thread is being instantiated the same way, so how I differentiate them?
Is there some better way to do this? I've looked into schedulers, future, etc., but it seems my case can just be done with simple threads.
I go the desired behavior with the following code:
use std::thread;
use std::sync::mpsc;
use std::collections::HashMap;
use rand::Rng;
use std::net::{Ipv4Addr,Ipv6Addr,IpAddr};
const RTT_ONE: IpAddr = IpAddr::V4(Ipv4Addr::new(127,0,0,1));
const RTT_TWO: IpAddr = IpAddr::V6(Ipv6Addr::new(0,0,0,0,0,0,0,1));
const RTT_THREE: IpAddr = IpAddr::V4(Ipv4Addr::new(127,0,1,1));//idk how ip adresses work, forgive if this in invalid but you get the idea
fn ping(address: IpAddr) -> f32 {
rand::thread_rng().gen_range(5.0..107.0)
}
fn main() {
let targets = [RTT_ONE,RTT_TWO,RTT_THREE];
let mut sample_rtts_map: HashMap<IpAddr,Vec<f32>> = HashMap::new();
for addr in targets.into_iter() {
let (sample_values,moved_values) = mpsc::channel();
let mut sampleRTTvalues: Vec<f32> = vec![];
thread::spawn(move || {
while sampleRTTvalues.len() < 100 {
let sampleRTT = ping(addr);
sampleRTTvalues.push(sampleRTT);
//thread::sleep(Duration::from_millis(5000));
}
});
sample_rtts_map.insert(addr,moved_values.recv().unwrap());
}
}
note that the use rand::Rng can be removed when implementing, as it is only so the example works. what this does is pass data from the spawned thread to the main thread, and in the method used it waits until the data is ready before adding it to the hash map. If this is problematic (takes a long time, etc.) then you can use try_recv instead of recv which will add an error / option type that will return a recoverable error if the value is ready when unwrapped, or return the value if it's ready
You can use a std::sync::mpsc channel to collect your data:
use std::collections::HashMap;
use std::sync::mpsc::channel;
use std::thread;
fn ping(_: &str) -> f32 { 0.0 }
fn main() {
let targets = ["a", "b"]; // just for example
let mut sample_rtts_map = HashMap::new();
let (tx, rx) = channel();
for addr in targets {
let tx = tx.clone();
thread::spawn(move || {
for _ in 0..100 {
let sampleRTT = ping(addr);
tx.send((addr, sampleRTT));
}
});
}
drop(tx);
// exit loop when all thread's tx have dropped
while let Ok((addr, sampleRTT)) = rx.recv() {
sample_rtts_map.entry(addr).or_insert(vec![]).push(sampleRTT);
}
println!("sample_rtts_map: {:?}", sample_rtts_map);
}
This will run all pinging threads simultaneously, and collect data in main thread synchronously, so that we can avoid using locks. Do not forget to drop sender in main thread after cloning to all pinging threads, or the main thread will hang forever.
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.
My actual non working main.rs (the closure needs to be non-mut)
the thread to wait for
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: >k::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.
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.
I'm trying to write a simple library that has a background worker thread that processes commands when the library functions are called.
The way that I would normally do it in C is have a global semaphore handle that the worker would block on. The functions would give the semaphore after they sent a command, at which point the worker would unblock etc... There are other ways but this is just an example.
I have a few questions about how I can achieve something similar with Rust.
How do I prevent a thread from closing once the function that created it returns? e.g the thread would be created when I call init(), but would exit when init() returns, how to prevent this?
How to have a have a global synchronization method between the worker thread and function calls? I was looking at using channels but how do I access the rx from the thread and multiple tx's from different functions? e.g send_cmd_a(), send_cmd_b() to the same thread
Pseudo code of what I'm trying to accomplish:
static (tx, rx) = mpsc::channel(); //how to do something like this?
fn init() {
thread::spawn(|| {
loop {
let cmd = rx.recv().unwrap(); //blocks till there is data
//process data....
if cmd == "exit" {
return;
}
}
});
}
fn send_cmd_a() {
//Do a bunch of other stuff...
tx.send("cmd_a").unwrap();
}
fn exit() {
tx.send("exit").unwrap();
}
Do I have to create one big object that encapsulates all of this, thus owning the synchronization mechanism? (still doesn't answer question #1)
What would be the preferred way of doing something like this in Rust?
I think I figured out a way to implement what I wanted in Rust without needing to use global variables.
struct Device {
sender: Sender<u8>, //other stuff
}
trait New {
fn new() -> Self;
}
trait SendCommand {
fn send_command(&self, u8);
}
impl New for Device {
fn new() -> Device {
let (tx, rx) = channel();
let device = Device { sender: tx };
thread::spawn(move || {
loop {
let cmd = rx.recv().unwrap();
println!("Command: {}", cmd); //process commands here
}
});
return device;
}
}
impl SendCommand for Device {
fn send_command(&self, cmd: u8) {
self.sender.send(cmd).unwrap();
}
}
fn main() {
let dev = Device::new(); //create the device
for i in 0..10 {
dev.send_command(i); //send commands
sleep(Duration::from_millis(50));
}
loop {}
}
One thread calculates some data that takes about 1GB of RAM and another thread only reads this data. What is the best way to implement this?
use std::thread;
use std::time::Duration;
fn main() {
let mut shared: i32 = 0; // will be changed to big structure
thread::spawn(move || {
loop {
shared += 1;
println!("write shared {}", shared);
thread::sleep(Duration::from_secs(2));
}
});
thread::spawn(move || {
loop {
thread::sleep(Duration::from_secs(1));
println!("read shared = ???"); // <---------------- ????
}
});
thread::sleep(Duration::from_secs(4));
println!("main");
}
You can run this code online (play.rust-lang.org)
The code and your statements don't really make sense together. For example, there's nothing that prevents the second thread from finishing before the first thread ever has a chance to start. Yes, I see the sleeps, but sleeping is not a viable concurrency solution.
For the question as asked, I'd use a channel. This allows one thread to produce a value and then transfer ownership of that value to another thread:
use std::thread;
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
let a = thread::spawn(move || {
let large_value = 1;
println!("write large_value {}", large_value);
tx.send(large_value).expect("Unable to send");
});
let b = thread::spawn(move || {
let large_value = rx.recv().expect("Unable to receive");
println!("read shared = {}", large_value);
});
a.join().expect("Unable to join a");
b.join().expect("Unable to join b");
println!("main");
}
For the code as presented, there's really no other options besides a Mutex or a RwLock. This allows one thread to mutate the shared value for a while, then the other thread may read it for a while (subject to the vagaries of the OS scheduler):
use std::thread;
use std::time::Duration;
use std::sync::{Arc, Mutex};
fn main() {
let shared = Arc::new(Mutex::new(0));
let shared_1 = shared.clone();
thread::spawn(move || {
loop {
let mut shared = shared_1.lock().expect("Unable to lock");
*shared += 1;
println!("write large_value {}", *shared);
}
});
thread::spawn(move || {
loop {
let shared = shared.lock().expect("Unable to lock");
println!("read shared = {}", *shared);
}
});
thread::sleep(Duration::from_secs(1));
println!("main");
}
None of this is particularly unique to Rust; channels are quite popular in Go and Clojure and mutexes have existed for A Very Long Time. I'd suggest checking out any of the numerous beginner's guides on the Internet for multithreading and the perils therein.
Editor's note — this example was created before Rust 1.0 and the specific types have changed or been removed since then. The general question and concept remains valid.
I have spawned a thread with an infinite loop and timer inside.
thread::spawn(|| {
let mut timer = Timer::new().unwrap();
let periodic = timer.periodic(Duration::milliseconds(200));
loop {
periodic.recv();
// Do my work here
}
});
After a time based on some conditions, I need to terminate this thread from another part of my program. In other words, I want to exit from the infinite loop. How can I do this correctly? Additionally, how could I to suspend this thread and resume it later?
I tried to use a global unsafe flag to break the loop, but I think this solution does not look nice.
For both terminating and suspending a thread you can use channels.
Terminated externally
On each iteration of a worker loop, we check if someone notified us through a channel. If yes or if the other end of the channel has gone out of scope we break the loop.
use std::io::{self, BufRead};
use std::sync::mpsc::{self, TryRecvError};
use std::thread;
use std::time::Duration;
fn main() {
println!("Press enter to terminate the child thread");
let (tx, rx) = mpsc::channel();
thread::spawn(move || loop {
println!("Working...");
thread::sleep(Duration::from_millis(500));
match rx.try_recv() {
Ok(_) | Err(TryRecvError::Disconnected) => {
println!("Terminating.");
break;
}
Err(TryRecvError::Empty) => {}
}
});
let mut line = String::new();
let stdin = io::stdin();
let _ = stdin.lock().read_line(&mut line);
let _ = tx.send(());
}
Suspending and resuming
We use recv() which suspends the thread until something arrives on the channel. In order to resume the thread, you need to send something through the channel; the unit value () in this case. If the transmitting end of the channel is dropped, recv() will return Err(()) - we use this to exit the loop.
use std::io::{self, BufRead};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
println!("Press enter to wake up the child thread");
let (tx, rx) = mpsc::channel();
thread::spawn(move || loop {
println!("Suspending...");
match rx.recv() {
Ok(_) => {
println!("Working...");
thread::sleep(Duration::from_millis(500));
}
Err(_) => {
println!("Terminating.");
break;
}
}
});
let mut line = String::new();
let stdin = io::stdin();
for _ in 0..4 {
let _ = stdin.lock().read_line(&mut line);
let _ = tx.send(());
}
}
Other tools
Channels are the easiest and the most natural (IMO) way to do these tasks, but not the most efficient one. There are other concurrency primitives which you can find in the std::sync module. They belong to a lower level than channels but can be more efficient in particular tasks.
The ideal solution would be a Condvar. You can use wait_timeout in the std::sync module, as pointed out by #Vladimir Matveev.
This is the example from the documentation:
use std::sync::{Arc, Mutex, Condvar};
use std::thread;
use std::time::Duration;
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = pair.clone();
thread::spawn(move|| {
let &(ref lock, ref cvar) = &*pair2;
let mut started = lock.lock().unwrap();
*started = true;
// We notify the condvar that the value has changed.
cvar.notify_one();
});
// wait for the thread to start up
let &(ref lock, ref cvar) = &*pair;
let mut started = lock.lock().unwrap();
// as long as the value inside the `Mutex` is false, we wait
loop {
let result = cvar.wait_timeout(started, Duration::from_millis(10)).unwrap();
// 10 milliseconds have passed, or maybe the value changed!
started = result.0;
if *started == true {
// We received the notification and the value has been updated, we can leave.
break
}
}
Having been back to this question several times myself, here's what I think addresses OP's intent and others' best practice of getting the thread to stop itself. Building on the accepted answer, Crossbeam is a nice upgrade to mpsc in allowing message endpoints to be cloned and moved. It also has a convenient tick function. The real point here is it has try_recv() which is non-blocking.
I'm not sure how universally useful it'd be to put a message checker in the middle of an operational loop like this. I haven't found that Actix (or previously Akka) could really stop a thread without--as stated above--getting the thread to do it itself. So this is what I'm using for now (wide open to correction here, still learning myself).
// Cargo.toml:
// [dependencies]
// crossbeam-channel = "0.4.4"
use crossbeam_channel::{Sender, Receiver, unbounded, tick};
use std::time::{Duration, Instant};
fn main() {
let (tx, rx):(Sender<String>, Receiver<String>) = unbounded();
let rx2 = rx.clone();
// crossbeam allows clone and move of receiver
std::thread::spawn(move || {
// OP:
// let mut timer = Timer::new().unwrap();
// let periodic = timer.periodic(Duration::milliseconds(200));
let ticker: Receiver<Instant> = tick(std::time::Duration::from_millis(500));
loop {
// OP:
// periodic.recv();
crossbeam_channel::select! {
recv(ticker) -> _ => {
// OP: Do my work here
println!("Hello, work.");
// Comms Check: keep doing work?
// try_recv is non-blocking
// rx, the single consumer is clone-able in crossbeam
let try_result = rx2.try_recv();
match try_result {
Err(_e) => {},
Ok(msg) => {
match msg.as_str() {
"END_THE_WORLD" => {
println!("Ending the world.");
break;
},
_ => {},
}
},
_ => {}
}
}
}
}
});
// let work continue for 10 seconds then tell that thread to end.
std::thread::sleep(std::time::Duration::from_secs(10));
println!("Goodbye, world.");
tx.send("END_THE_WORLD".to_string());
}
Using strings as a message device is a tad cringeworthy--to me. Could do the other suspend and restart stuff there in an enum.