I skimmed the latter parts of this post since I felt like I read it all before, but I think moro
is new to me. I was intrigued to find out how scoped span
exactly behaves.
async fn slp() {
tokio::time::sleep(std::time::Duration::from_millis(1)).await
}
async fn _main() {
let value = 22;
let result_fut = moro::async_scope!(|scope| {
dbg!(); // line 8
let future1 = scope.spawn(async {
slp().await;
dbg!(); // line 11
let future2 = scope.spawn(async {
slp().await;
dbg!(); // line 14
value // access stack values that outlive scope
});
slp().await;
dbg!(); // line 18
let v = future2.await * 2;
v
});
slp().await;
dbg!(); // line 25
let v = future1.await * 2;
slp().await;
dbg!(); // line 28
v
});
slp().await;
dbg!(); // line 32
let result = result_fut.await;
eprintln!("{result}"); // prints 88
}
fn main() {
// same output with `new_current_thread()` of course
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
rt.block_on(_main())
}
This prints:
[src/main.rs:32:5]
[src/main.rs:8:9]
[src/main.rs:25:9]
[src/main.rs:11:13]
[src/main.rs:18:13]
[src/main.rs:14:17]
[src/main.rs:28:9]
88
So scoped spawn
doesn’t really spawn tasks as one might mistakenly think!
The most interesting part here is the polling only has to take place on the scope itself. That was actually what I wanted to check, but got distracted because all spawns are awaited in the scope in
moro
’s README example.async fn slp() { tokio::time::sleep(std::time::Duration::from_millis(1)).await } async fn _main() { let result_fut = moro::async_scope!(|scope| { dbg!("d1"); scope.spawn(async { dbg!("f1a"); slp().await; slp().await; slp().await; dbg!("f1b"); }); dbg!("d2"); // 11 scope.spawn(async { dbg!("f2a"); slp().await; slp().await; dbg!("f2b"); }); dbg!("d3"); // 14 scope.spawn(async { dbg!("f3a"); slp().await; dbg!("f3b"); }); dbg!("d4"); async { dbg!("b1"); } // never executes }); slp().await; dbg!("o1"); let _ = result_fut.await; } fn main() { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap(); rt.block_on(_main()) }
[src/main.rs:32:5] "o1" = "o1" [src/main.rs:7:9] "d1" = "d1" [src/main.rs:15:9] "d2" = "d2" [src/main.rs:22:9] "d3" = "d3" [src/main.rs:28:9] "d4" = "d4" [src/main.rs:9:13] "f1a" = "f1a" [src/main.rs:17:13] "f2a" = "f2a" [src/main.rs:24:13] "f3a" = "f3a" [src/main.rs:26:13] "f3b" = "f3b" [src/main.rs:20:13] "f2b" = "f2b" [src/main.rs:13:13] "f1b" = "f1b"
The non-awaited jobs are run concurrently as the moro docs say. But what if we immediately await f2?
[src/main.rs:32:5] "o1" = "o1" [src/main.rs:7:9] "d1" = "d1" [src/main.rs:15:9] "d2" = "d2" [src/main.rs:9:13] "f1a" = "f1a" [src/main.rs:17:13] "f2a" = "f2a" [src/main.rs:20:13] "f2b" = "f2b" [src/main.rs:22:9] "d3" = "d3" [src/main.rs:28:9] "d4" = "d4" [src/main.rs:24:13] "f3a" = "f3a" [src/main.rs:13:13] "f1b" = "f1b" [src/main.rs:26:13] "f3b" = "f3b"
f1 and f2 are run concurrently, f3 is run after f2 finishes, but doesn’t have to wait for f1 to finish, which is maybe obvious, but… (see below).
So two things here:
defer_to_scope()
be confusing if the job is awaited in the scope?