Search code examples
rustasync-awaitrust-tokio

failed to run two threads using #[tokio::main] macro


I am trying to understand how tokio runtime works, i created two runtimes(on purpose) using #[tokio::main] macro, the first should executes function a() and the second executes function b().

I am assuming that they should be both printing "im awake A" and "im awake B" simultaniosuly forever (since they are calling a function that has a loop async_task), however that is not the case, it only prints "im awake A".

since each runtime has its own thread pool; why they are not running in parallel?

use std::thread;
fn main() {
    a();
    b();
}

#[tokio::main]
async fn a() {
    tokio::spawn(async move { async_task("A".to_string()).await });
}

pub async fn async_task(msg: String) {
    loop {
        thread::sleep(std::time::Duration::from_millis(1000));
        println!("im awake {}", msg);
    }
}
#[tokio::main]
async fn b() {
    tokio::spawn(async move { async_task("B".to_string()).await });
}

Solution

  • Calling a(); from the synchronous main function will block until a() finishes. Check out the documentation here: https://docs.rs/tokio/1.2.0/tokio/attr.main.html

    #[tokio::main] 
    async fn main() {
        println!("Hello world"); 
    }
    

    Equivalent code not using #[tokio::main]

      fn main() {
        tokio::runtime::Builder::new_multi_thread()
            .enable_all()
            .build()
            .unwrap()
            .block_on(async {
                println!("Hello world");
            }) }
    

    To get your example to work, main() could also spawn 2 threads that run a, b and wait for them to finish:

    fn main() {
        let t1 = thread::spawn(|| {
            a();
        });
        let t2 = thread::spawn(|| {
            b();
        });
    
        t1.join().unwrap();
        t2.join().unwrap();
    }
    

    EDIT: Note that a() and b() also do not need to use tokio::spawn as they're already executing in their own async runtime.

    #[tokio::main]
    async fn a() -> Result<(), JoinError> {
        async_task("A".to_string()).await
    }
    
    #[tokio::main]
    async fn b() {
       async_task("B".to_string()).await
    }
    

    If you use tokio::spawn in a and b, you would need to await the spawned future, but tokio::spawn(task.await).await is basically the same as just doing task.await.