Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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;
} // ()
like image 737
cibercitizen1 Avatar asked Dec 29 '25 13:12

cibercitizen1


2 Answers

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.

like image 100
cibercitizen1 Avatar answered Jan 01 '26 18:01

cibercitizen1


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.

like image 26
lkolbly Avatar answered Jan 01 '26 18:01

lkolbly



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!