ezio
ezio

Reputation: 548

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 });
}

Upvotes: 3

Views: 1093

Answers (3)

Tyler Aldrich
Tyler Aldrich

Reputation: 416

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.

Upvotes: 3

pantalohnes
pantalohnes

Reputation: 1046

Take a look at the documentation for the main macro. There's a clue to why this doesn't work there.

Note: This macro can be used on any function and not just the main function. Using it on a non-main function makes the function behave as if it was synchronous by starting a new runtime each time it is called. If the function is called often, it is preferable to create the runtime using the runtime builder so the runtime can be reused across calls.

So you can use it on multiple functions, but what that means is that you need to call each one in a separate main function. You could also manually construct it

fn main() {
    let jh1 = std::thread::spawn(|| a());
    let jh2 = std::thread::spawn(|| b());
    jh1.join().unwrap();
    jh2.join().unwrap();
}

async fn async_task(msg: String) {
    loop {
        tokio::time::sleep(core::time::Duration::from_secs(1)).await;
        println!("I'm awake {}", msg);
    }
}

#[tokio::main]
async fn a() {
    async_task("a".to_owned()).await
}

#[tokio::main]
async fn b() {
    async_task("b".to_owned()).await
}

Upvotes: 1

Chayim Friedman
Chayim Friedman

Reputation: 71260

#[tokio::main] expands to a call to Runtime::block_on(), and as said in its docs (emphasis mine):

This runs the given future on the current thread, blocking until it is complete, and yielding its resolved result.

If you use Runtime::spawn() instead (and make sure not to drop the runtime because it shuts it down), it prints both from A and B correctly:

fn main() {
    let _a_runtime = a();
    b();
}

fn a() -> tokio::runtime::Runtime {
    let runtime = tokio::runtime::Runtime::new().unwrap();
    runtime.spawn(async { async_task("A".to_string()).await });
    runtime
}

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

Upvotes: 1

Related Questions