Reputation: 548
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
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
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
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