Difference between std::⁢thread::spawn and async_std::task

Is there a way to write these two lines using std::thread::spawn that would be similar to the ones before it that use async_std::task::spawn? Maybe the issue is that I need to pass closures to std::thread::spawn and async closures aren't supported?

https://github.com/mvolkmann/rust-futures/blob/f62b96f57c58cac131c0af5d1358eb607d41edb5/src/main.rs#L48

thread::spawn accepts a synchronous closure, whereas task::spawn accepts a future, and an async block is a future. In order to run a future to completion inside a synchronous function or closure, you can use a block_on function - for example async_std::task::block_on. That would look like thread::spawn(|| block_on(async { ... })).

2 Likes

I'm making progress! What I really want to do is demonstrate what I see as the four options for executing multiple functions: serially, concurrently on the same thread, in parallel using OS threads, and in parallel using green threads (tasks). My code now demonstrates all of these options, but I'm not happy with the approach to using OS threads. Can this be improved?

https://github.com/mvolkmann/rust-futures/blob/07293b94ac9e0b70cf81e47f72e6f0beeb4795f8/src/main.rs#L46

The OS thread code is actually fine, it's just weird because you typically do not combine async with OS threads (that's generally managed by the runtime).

Don't use join! or join_all on task handles - just await each sequentially like you do with the OS thread task handles. The reason is that tasks do not need polling to make progress, and join will poll them more than necessary.

I was hoping you would say there is a way to simplify the OS threads code, especially the use of the double unwraps to get the results. It feels like I must be overlooking a better approach. Am I not forced into making the sum_file function async because of my use of a stream for reading from the file?

You don't need to use block_on and async just to run a thing in another thread... just run the thing.

use std::thread;
use std::io::{self, BufRead};
use std::fs::File;

fn sum_file_sync(file_path: &str) -> io::Result<f64> {
    let f = File::open(file_path)?;
    let reader = io::BufReader::new(f);
    let mut sum = 0.0;
    for line in reader.lines() {
        if let Ok(n) = line?.parse::<f64>() {
            println!("{}", n);
            sum += n;
        }
    }
    Ok(sum)
}

fn main() {
    let handle1 = thread::spawn(|| sum_file_sync("./numbers1.txt"));
    let handle2 = thread::spawn(|| sum_file_sync("./numbers3.txt"));
    
    let sum1 = handle1.join().unwrap().unwrap();
    let sum2 = handle2.join().unwrap().unwrap();

    println!("sum1 = {:?}, sum2 = {:?}", sum1, sum2);
}

You don't have to use async for concurrency; it's a convenient abstraction in some cases but it's not mandatory.

The double .unwrap() is a little unfortunate there, but it's a consequence of not really having anything else to do with the error; if you let main return an io::Result you can turn two of them into ?s:

fn main() -> io::Result<()> {
    let handle1 = thread::spawn(|| sum_file_sync("./numbers1.txt"));
    let handle2 = thread::spawn(|| sum_file_sync("./numbers3.txt"));
    
    let sum1 = handle1.join().unwrap()?;
    let sum2 = handle2.join().unwrap()?;

    println!("sum1 = {:?}, sum2 = {:?}", sum1, sum2);
    Ok(())
}

But that's only barely better; it still just terminates the program with a pretty underwhelming error message. If you want to get rid of the unwraps you need to replace them with something -- a match to handle the error case, or a more friendly error type with custom formatting, usually.

1 Like

I see that this is an improvement, but I wish I didn't have to write a new version of the sum_file function in order to make it easier to use with std::thread::spawn. I'd prefer to be able to demonstrate all four approaches using the same sum_file function. But perhaps this is the best that can be done.

I mean... you can use block_on, it will work. It's just awkward because you're using the clunky async machinery to run code synchronously for no reason. In the real world you should rarely if ever need to do such a thing.

1 Like

I see now that I didn't need the async block and .await in order to use task::block_on. This is a bit simpler:
https://github.com/mvolkmann/rust-futures/blob/8332f181efe144d765d574cafb4ba3f7dd3049c9/src/main.rs#L71
But I see your point that I should just use a synchronous version of the sum_file function if I'm running it in its own thread.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.