Handling async function in a thread

This is a simple example of a worker thread that produces an output, a String, and sends it to the main thread for presentation. It works fine.

use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc::channel;
use std::thread;
use std::time::Duration;

fn main() {
    let (tx, rx): (Sender<String>, Receiver<String>) = channel();
        thread::spawn( move || {
        loop {
            let mess = task();
            tx.send(mess).unwrap();
            thread::sleep(Duration::from_millis(1000));
        }
    });

    loop {
        // Hang until signal received
        let rec = rx.recv().unwrap();
        println!("received {:#?}", rec );
    }  
}

fn task() -> String {
    "Hello world".to_string()
}

But in my case task() makes a web access and must be async. When I add async to the declaration: async fn task() -> String everything changes. task() returns a future and must be awaited. Then the thread must be async but the compiler reports "async closures are unstable".
I use block_on() instead:

        thread::spawn( move || {
        loop {
            let mess = task();
            block_on(mess);
            tx.send(mess).unwrap();
            thread::sleep(Duration::from_millis(1000));
        }

Then the compiler reports type mismatch between send(), that wants String, and task() that now returns impl Future<Output = String>. How do I solve this jumble?

Moreover, I suspect that block_on(mess) borrows mess and makes it unavailable for send().

.

First off, if you are manually spawning a new thread anyway, then you might as well make that task synchronous/blocking; it won't block the other threads.

It doesn't; it moves it. That's specifically not borrowing. Borrowing (as the physical analogy suggests) means you eventually give it back, i.e., it's a temporary thing. Moving is permanent – once you moved the value out of a place, you can't use the place anymore.

However, that's not a problem. block_on() (which I'm assuming is futures::executor::block_on()) returns the result of the Future. No other API would really make sense, if you think about it. Accordingly, this compiles and runs.


All of this isn't particularly elegant or efficient, though (manually spinning in a loop and blocking). If you need to perform asynchronous operations, just go all the way async and choose a multi-threaded runtime which will dispatch your futures on a thread pool automatically, to ensure you benefit from parallelism whenever applicable.

3 Likes

Thank you very much, Árpád. The only change I can see in the code is moving block_on: let mess = block_on(task()). It solves the type mismatch in a magical way that I don't understand, but I can live with it. A more realistic thread usage would look like this:

    thread::spawn( move || {
        let tick = schedule_recv::periodic_ms(60000);
        loop {
            tick.recv().unwrap();
            let mess = block_on(task());
            tx.send(mess).unwrap();
        }
    });

The thread will wake up once every minute, make a web request, process the data, and then sleep until the next minute. I will not spawn many threads, just this one. If you think this design is inferior, I would be grateful for an improvement suggestion. But I'm quite new to Rust and would need some details.

This is what I meant by:

Please read the documentation, this is trivial to infer if you merely look at the signature.