I am learning Rust and trying to make a simple egui GUI app that polls a telnet host in a seperate thread, to avoid the main thread locking the GUI. I am using the telnet crate for the client.
Here is the code where I am having issues:
struct TelnetApp {
gui_status: AppView,
telnet_ip: String,
telnet_port: String,
telnet_connect_failed_display: bool,
telnet_connect_failed_message: String,
telnet_client: Arc<Mutex<Option<Telnet>>>,
telnet_result : Arc<Mutex<String>>,
}
impl TelnetApp {
// Called from gui after successfully connecting to Telnet host
fn start_telnet_loop(&mut self) {
let arc_telnet_result = self.telnet_result.clone();
let arc_telnet_client = self.telnet_client.clone();
let time = SystemTime::now();
thread::spawn(move || { // <---- ERROR: `(dyn Stream 'static)` cannot be sent between threads safely
loop {
thread::sleep(Duration::from_millis(1000));
arc_telnet_client.lock().unwrap().unwrap().read(); // <--- This line causes error
{
// I can read and modify the String, presumably because it implements Send?
*arc_telnet_result.lock().unwrap() = String::from(format!("Time {}", time.elapsed().unwrap().as_micros()));
}
}
});
}
}
As i marked with a comment, the thread spawn line gives me an error, which seems to stem from the fact that arc_telnet_client does not implement the trait "Send", as the error goes away when removing the line:
arc_telnet_client.lock().unwrap().unwrap().read()
I read that wrapping in Arc<Mutex<>>
is the recommended way to handle multithreading, but this does still not give the trait Send.
Why is my approach not allowed, even when I am using a mutex to lock it? How would you implement a simple polling thread like this?
CodePudding user response:
Why is my approach not allowed, even when I am using a mutex to lock it?
Because !Send
means the object is not safe to move to a different thread at all. It doesn't matter how you protect it, it's just not valid.
For instance it might be using threadlocal data, or kernel task resources, or some sort of unprotected global or shared state, or affinity mechanics. No matter how or why, if it's !Send
, it can only be accessed from and by the thread where it was created, doesn't matter what you wrap it in. An example of that is MutexGuard
:
impl<T: ?Sized> !Send for MutexGuard<'_, T>
That is because it's common for mutexes to only be unlockable from the thread which locked them (it's UB in posix, the release fails on windows)
As its traits describe, a Mutex is Sync (so can be shared between threads) if an object is Send
:
impl<T: ?Sized Send> Sync for Mutex<T>
That is because semantically, having a mutex around a Send is equivalent to moving the wrapped object from one thread to the next through a channel (the wrapped object will always be used from one thread at a time).
RwLock, on the other hand, requires Sync
since the wrapped object can be concurrently accessed from different threads (in read mode):
impl<T: ?Sized Send Sync> Sync for RwLock<T>