Async /await or thread for pulling data from SerialPort

I have a library I'm working on that pulls data from the serial port using the SerialPort crate. SerialPort blocks the thread while waiting for new data, so I started by running it on a separate thread. This worked well until we did some refactoring where I needed to call a method on self with the pulled data from within the spawned thread. This caused all sorts of issues which ended up with me wrapping a reference to self in an Arc<Mutex> but I hit a wall with the lifetime checker (still learning Rust!).

I then refactored to use mpsc channels, where the IO thread sends it's data to the main thread for processing. This works, but it now blocks the main thread, I assume because of the tight loop where it calls try_recv:

    fn io_begin(&mut self)
    {
        match self.serial_port.take() {
            Some(mut serial_port) => {

                let (data_tx, data_rx) = mpsc::channel();

                let builder = thread::Builder::new()
                    .name("serial_port".into());

                let port_name = self.port_name.clone();
                let baud_rate = self.baud_rate;

                builder.spawn(move || {

                    let mut serial_buf: Vec<u8> = vec![0; 2048];
                    println!("Receiving data on {} at {} baud:", port_name, baud_rate);
                    loop {
                        match serial_port.read(serial_buf.as_mut_slice()) {
                            Ok(bytes_read) => {

                                data_tx.send(serial_buf.as_mut_slice()[0..bytes_read].to_vec());
                            },
                            // TODO log errors/call error callback
                            Err(ref e) if e.kind() == io::ErrorKind::TimedOut => (),
                            Err(e) => eprintln!("{:?}", e),
                        }
                    }
                }).unwrap();

                loop { // TODO This is currently blocking the UI thread! 
                    let received_data = data_rx.try_recv();
                    if received_data.is_ok() {
                        self.connection_struct.process_received_data(received_data.unwrap());
                    }
                }
            }
            None => {
                println!("port not opened");
            }
        }
    

So looking into async / await and Futures, I wonder if I could wrap the call to serial_port.read in a Future or async fn and call await on it, potentially negating the need to spawn a thread at all?

Or alternatively, build a non-blocking timer that awaits until new data is available from the thread rather than spinning on try_recv?

This is not really possible. The function is blocking and there isn't much you can do about that. Perhaps tokio-serial would be able to replace it?

There is a method called blocking which is similar to starting a thread pool, except that it is managed by Tokio, but I don't know if it still exists in Tokio 0.2

Thanks, but I'd like to avoid using tokio, as it seems overcomplex for the needs of our project.

If you need any advice in working with your thread based model, feel free to ask.

Yes please! I'll change the title accordingly.

The issue with the thread based model is the need to call a mut method on self either from within the thread (which requires a mutex due to one of it's members not being Send) or as I have in my example, using channels to send the data when its ready. What I want to be able to do is poll with try_recv without blocking the thread, possibly using a non-blocking timer.

I don't understand why its currently blocking as I assumed try_recv should not block, but perhaps the tight spinning loop is what's blocking the thread.