cibercitizen1
cibercitizen1

Reputation: 21496

Concurrent async/await with sleep

I would like to know if the answer to this rather old question about futures still applies to the more recent language constructs async/await. It seems to be so, since code below prints:

hello 
good bye 
hello

although the guide says

The futures::join macro makes it possible to wait for multiple different futures to complete while executing them all concurrently.

Clearly, it's a diversion of the expected behavior in many, many other asynchronous systems (node.js for example), with regard to sleep.

Any fundamental reason to be that way?

use std::time::Duration;
use std::thread;

async fn sayHiOne() {
    println!( " hello " );
    thread::sleep( Duration::from_millis( 3000 ) );
    println!( " good bye " );
} // ()

async fn sayHiTwo() {
    println!( " hello " );
} // ()

async fn mainAsync() {

    let fut1 = sayHiOne();

    let fut2 = sayHiTwo();

    futures::join!( fut1, fut2 );
} // ()

fn main() {
    block_on( mainAsync() );
} // ()

Addition: the behavior (I) expected with actual threads

fn main() {

    let fut1 = do_async( move || {
        println!( "hello" );
        thread::sleep( Duration::from_millis( 3000 ) );
        println!( "good bye" );
    });

    let fut2 = do_async( move || {
        println!( "hello" );
    });

    fut1();
    fut2();

}

use std::thread;
use std::time::Duration;
use std::sync::mpsc::channel;


fn do_async<TOut, TFun>( foo: TFun ) -> (impl FnOnce()-> TOut)
 where
    TOut: Send + Sync + 'static,
    TFun: FnOnce() -> TOut + Send + Sync + 'static
    
{

    let (sender, receiver) 
        = channel::< TOut >();

    let hand = thread::spawn(move || {
        sender.send( foo() ).unwrap(); 
    } );

    let f = move || -> TOut {
        let res = receiver.recv().unwrap();
        hand.join().unwrap();
        return res;
    };

    return f;
} // ()

Upvotes: 3

Views: 11872

Answers (2)

cibercitizen1
cibercitizen1

Reputation: 21496

Since the standard/original thread::sleep is blocking, it turns out that the async library is providing async_std::task::sleep( ... ) which is the nonblocking version for sleep. It's to be used with .await(no parentheses):

task::sleep(Duration::from_millis(3000)).await;

This sleep has the same effect that unstable version: yield_now in the sense that it

moves the currently executing future to the back of the execution queue, making room for other futures to execute. This is especially useful after running CPU-intensive operations inside a future.

So I guess, the intended use is to "kindly" share the use of the thread among the futures, whenever a task is planning to perform a long work.

Upvotes: 6

lkolbly
lkolbly

Reputation: 1240

Yes, it still applies. It fundamentally has to be that way because, like the linked answer says, each async function will be running on the same thread - std::thread::sleep knows nothing about async, and so will make the whole thread sleep.

Nodejs (and JavaScript in general) is much more designed around async, so the language primitives and the language runtime are more async-aware in that way.

Upvotes: 2

Related Questions