What is the difference between tokio::spawn(my_future).await and just my_future.await? - rust

Given an async function and it's corresponding future, lets say:
async fn foo() -> Result<i32, &'static str> {
// ...
}
let my_future = foo();
what is the difference between awaiting it using just .await other than using tokio::spawn().await?
// like this...
let result1 = my_future.await;
// ... and like this
let result2 = tokio::spawn(my_future).await;

One typically doesn't await a spawned task (or at least not right away). It's more common to simply write:
tokio::spawn(my_future);
Leave out the .await and the task will run in the background while the current task continues. Immediately calling .await blocks the current task. spawn(task).await is effectively no different than task.await. It's akin to creating a thread and immediately joining it, which is equally pointless.
Spawned tasks don't need to be awaited the way bare futures do. Awaiting them is optional. When might one want to await one, then? If you want to block the current task until the spawned task finishes.
let task = tokio::spawn(my_future);
// Do something else.
do_other_work();
// Now wait for the task to complete, if it hasn't already.
task.await;
Or if you need the result, but need to do work in between starting the task and collecting the result.
let task = tokio::spawn(my_future);
// Do something else.
do_other_work();
// Get the result.
let result = task.await;

Related

How to terminate a blocking tokio task?

In my application I have a blocking task that synchronically reads messages from a queue and feeds them to a running task.
All of this works fine, but the problem that I'm having is that the process does not terminate correctly, since the queue_reader task does not stop.
I've constructed a small example based on the tokio documentation at: https://docs.rs/tokio/1.20.1/tokio/task/fn.spawn_blocking.html
use tokio::sync::mpsc;
use tokio::task;
#[tokio::main]
async fn main() {
let (incoming_tx, mut incoming_rx) = mpsc::channel(2);
// Some blocking task that never ends
let queue_reader = task::spawn_blocking(move || {
loop {
// Stand in for receiving messages from queue
incoming_tx.blocking_send(5).unwrap();
}
});
let mut acc = 0;
// Some complex condition that determines whether the job is done
while acc < 95 {
tokio::select! {
Some(v) = incoming_rx.recv() => {
acc += v;
}
}
}
assert_eq!(acc, 95);
println!("Finalizing thread");
queue_reader.abort(); // This doesn't seem to terminate the queue_reader task
queue_reader.await.unwrap(); // <-- The process hangs on this task.
println!("Done");
}
At first I expected that queue_reader.abort() should terminate the task, however it doesn't. My expectation is that tokio can only do this for tasks that use .await internally, because that will handle control over to tokio. Is this right?
In order to terminate the queue_reader task I introduced a oneshot channel, over which I signal the termination, as shown in the next snippet.
use tokio::task;
use tokio::sync::{oneshot, mpsc};
#[tokio::main]
async fn main() {
let (incoming_tx, mut incoming_rx) = mpsc::channel(2);
// A new channel to communicate when the process must finish.
let (term_tx, mut term_rx) = oneshot::channel();
// Some blocking task that never ends
let queue_reader = task::spawn_blocking(move || {
// As long as termination is not signalled
while term_rx.try_recv().is_err() {
// Stand in for receiving messages from queue
incoming_tx.blocking_send(5).unwrap();
}
});
let mut acc = 0;
// Some complex condition that determines whether the job is done
while acc < 95 {
tokio::select! {
Some(v) = incoming_rx.recv() => {
acc += v;
}
}
}
assert_eq!(acc, 95);
// Signal termination
term_tx.send(()).unwrap();
println!("Finalizing thread");
queue_reader.await.unwrap();
println!("Done");
}
My question is, is this the canonical/best way to do this, or are there better alternatives?
Tokio cannot terminate CPU-bound/blocking tasks.
It is technically possible to kill OS threads, but generally it is not a good idea, as it's expensive to create new threads and it can leave your program in an invalid state. Even if Tokio decided this was something worth implementing, it would serverely limit its implementation - it would be forced into a multithread model, just to support the possibility that you'd want to kill a blocking task before it's finished.
Your solution is pretty good; give your blocking task the responsibility for terminating itself and provide a way to tell it to do so. If this future was part of a library, you could abstract the mechanism away by returning a "handle" to the task that had a cancel() method.
Are there better alternatives? Maybe, but that would depend on other factors. Your solution is good and easily extended, for example if you later needed to send different types of signal to the task.

why not panic when task timeout?

I set the timeout to 1s, but the task executes to 3s, but no panic occurs.
#code
#[should_panic]
fn test_timeout() {
let rt = create_runtime();
let timeout_duration = StdDuration::from_secs(1);
let sleep_duration = StdDuration::from_secs(3);
let _guard = rt.enter();
let timeout = time::timeout(timeout_duration, async {
log("timeout running");
thread::sleep(sleep_duration);
log("timeout finsihed");
"Ding!".to_string()
});
rt.block_on(timeout).unwrap();
}
Using thread::sleep in asynchronous code is almost always wrong.
Conceptually, the timeout works like this:
tokio spawns a timer which would wake up after the specified duration.
tokio spawns your future. If it returns Poll::Ready, timer is thrown away and the future succeeds. If it returns Poll::Pending, tokio waits for the next event, i.e. for wakeup of either your future or the timer.
If the future wakes up, tokio polls it again. If it returns Poll::Ready - again, timer is thrown away, future succeeds.
If the timer wakes up, tokio polls the future one last time; if it's still Poll::Pending, it times out and is not polled anymore, and timeout returns an error.
In your case, however, future do not return Poll::Pending - it blocks inside the thread::sleep. So, even though the timer could fire after one second has passed, tokio has no way to react - it waits for the future to return, future returns only after the thread is unblocked, and, since there's no await inside the block, it returns Poll::Ready - so the timer isn't even checked.
To fix this, you're expected to use tokio::time::sleep for any pauses inside async code. With it, the future times out properly. To illustrate this claim, let's see the self-contained example equivalent to your original code:
use core::time::Duration;
use tokio::time::timeout;
#[tokio::main]
async fn main() {
let timeout_duration = Duration::from_secs(1);
let sleep_duration = Duration::from_secs(3);
timeout(timeout_duration, async {
println!("timeout running");
std::thread::sleep(sleep_duration);
println!("timeout finsihed");
"Ding!".to_string()
})
.await
.unwrap_err();
}
Playground
As you've already noticed, this fails - unwrap_err panics when called on Ok, and timeout returns Ok since the future didn't time out properly.
But when replacing std::thread::sleep(...) with tokio::time::sleep(...).await...
use core::time::Duration;
use tokio::time::timeout;
#[tokio::main]
async fn main() {
let timeout_duration = Duration::from_secs(1);
let sleep_duration = Duration::from_secs(3);
timeout(timeout_duration, async {
println!("timeout running");
tokio::time::sleep(sleep_duration).await;
println!("timeout finsihed");
"Ding!".to_string()
})
.await
.unwrap_err();
}
...we get the expected behavior - playground.

Rust: can tokio be understood as similar to Javascripts event loop or be used like it?

I'm not sure if tokio is similar to the event loop in Javascript, also a non-blocking runtime, or if it can be used to work in a similar way. In my understanding, tokio is an runtime for futures in Rust. Therefore it must implement some kind of userland threads or tasks, which can be achieved with an event loop (at least partly) to schedule new tasks.
Let's take the following Javascript code:
console.log('hello1');
setTimeout(() => console.log('hello2'), 0);
console.log('hello3');
setTimeout(() => console.log('hello4'), 0);
console.log('hello5');
The output will be
hello1
hello3
hello5
hello2
hello4
How can I do this in tokio? Is tokio meant to work like this overall? I tried the following code
async fn set_timeout(f: impl Fn(), ms: u64) {
tokio::time::sleep(tokio::time::Duration::from_millis(ms)).await;
f()
}
#[tokio::main]
async fn main() {
println!("hello1");
tokio::spawn(async {set_timeout(|| println!("hello2"), 0)}).await;
println!("hello3");
tokio::spawn(async {set_timeout(|| println!("hello4"), 0)}).await;
println!("hello5");
}
The output is just
hello1
hello3
hello5
If I change the code to
println!("hello1");
tokio::spawn(async {set_timeout(|| println!("hello2"), 0)}.await).await;
println!("hello3");
tokio::spawn(async {set_timeout(|| println!("hello4"), 0)}.await).await;
println!("hello5");
The output is
hello1
hello2
hello3
hello4
hello5
but then I don't get the point of the whole async/await/future feature, because then my "async" set_timeout-tasks are actually blocking the other println statements..
In short: yes, Tokio is meant to work much like the JavaScript event loop. However, there are three problems with your first snippet.
First, it returns from main() before waiting for things to play out. Unlike your JavaScript code, which presumably runs in the browser, and runs the timeouts even after the code you typed in the console has finished running, the Rust code is in a short-lived executable which terminates after main(). Whatever things were scheduled to happen later won't occur if the executable stops running because it returned from main().
The second issue is that the anonymous async block that calls the set_timeout() async function doesn't do anything with its return value. An important difference between async functions in Rust and JavaScript is that in Rust you can't just call an async function and be done with it. In JavaScript an async function returns a promise, and if you don't await that promise, the event loop will still execute the code of the async function in the background. In Rust, an async function returns a future, but it is not associated with any event loop, it is just prepared for someone to run it. You then need to either await it with .await (with the same meaning as in JavaScript) or explicitly pass it to tokio::spawn() to execute in the background (with the same meaning as calling but not awaiting the function in JavaScript). Your async block does neither, so the invocation of set_timeout() is a no-op.
Finally, the code immediately awaits the task created by spawn(), which defeats the purpose of calling spawn() in the first place - tokio::spawn(foo()).await is functionally equivalent to foo().await for any foo().
The first issue can be resolved by adding a tiny sleep at the end of main. (This is not the proper fix, but will serve to demonstrate what happens.) The second issue can be fixed by removing the async block and just passing the return value of set_timeout() to tokio::spawn(). The third issue is resolved by removing the unnecessary .await of the task.
#[tokio::main]
async fn main() {
println!("hello1");
tokio::spawn(set_timeout(|| println!("hello2"), 0));
println!("hello3");
tokio::spawn(set_timeout(|| println!("hello4"), 0));
println!("hello5");
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
}
This code will print the "expected" 1, 3, 5, 4, 2 (although the order is not guaranteed in programs like this). Real code would not end with a sleep; instead, it would await the tasks it has created, as shown in Shivam's answer.
Unlike JavaScript, Rust does not start the execution of an async function until the future is awaited. It means set_timeout(|| println!("hello2"), 0) only creates a new future. It doesn't execute it at all. When you await it, only then it is executed. .await essentially blocks the current thread until the future is completed which is not "real asynchronous applications". To make your code concurrent like JavaScript, you can use join! macro:-
use tokio::join;
use tokio::time::*;
async fn set_timeout(f: impl Fn(), ms: u64) {
sleep(Duration::from_millis(ms)).await;
f()
}
#[tokio::main]
async fn main() {
println!("hello1");
let fut_1 = tokio::spawn(set_timeout(|| println!("hello2"), 0));
println!("hello3");
let fut_2 = tokio::spawn(set_timeout(|| println!("hello4"), 0));
println!("hello5");
join!(fut_1, fut_2);
}
You can use FuturesOrdered if want to take feel of Promise.all.
More info:-
https://news.ycombinator.com/item?id=21473777
https://rust-lang.github.io/async-book/06_multiple_futures/01_chapter.html

Why doesn't the second await get called on tokio::spawn?

I have the following function that connects to a database using sqlx):
async fn testConnect() -> anyhow::Result<PgPool> {
delay_for(Duration::from_millis(3000)).await;
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&"database_connection_string")
.await?;
Ok(pool)
}
And I run it on the tokio runtime:
let mut threaded_rt = runtime::Builder::new()
.threaded_scheduler()
.enable_all()
.build()
.unwrap();
threaded_rt.block_on(future::lazy(move |_| {
let handle = tokio::spawn(testConnect());
return handle;
}));
Any code after delay_for inside testConnect does not get executed. Why is this and how can I make both awaits run?
If I remove the delay_for line of code, the database connection code runs as expected.
I suspect that the following happens. This is analogue to starting a background worker thread and quit without joining it.
you spawn the task on tokio and return the handle
block_on drives the tokio reactor for a little while which is just enough for a normal connection, but not enough for the delay to expire
nothing drives the reactor anymore, so the result of the spawned task is just dropped and the program exits
If so, you can fix it simply by calling threaded_rt.block_on(testConnect()) directly, the spawn() part seems to be completely pointless.

what is the meaning of "await" used in Rust?

This question may somewhat relate more to async-programming than Rust.But after googling a lot, there are still somepoint I think is missing. And since I am learning Rust, I would put it in a Rust way.
Let me give my understanding of async-programming first---After all, this is the basis, maybe I am wrong or not:
To make program run efficiently, dealing tasks concurrently is essential. Then thread is used, and the thread could be joined whenever the data from the thread is needed. But thread is not enough to handle many tasks,like a server does. Then thread-pool is used, but how to fetch data when it is needed with no information of which thread should be waiting for? Then callback function(cb for short) comes up.With cb,only what needs to do in cb should be considered. In addition, to make cpu little overhead, green thread comes up.
But what if the asyn-waiting things need to do one after another, which leads to "callback hell"? Ok, the "future/promise" style comes up, which let code looks like sync-code, or maybe like a chain(like in javascript). But still the code looks not quite nice. Finally, the "async/await" style comes up, as another syntactic sugar for "future/promise" style. And usually, the "async/await" with green thread style is called "coroutine", be it using only one native thread or multi-native threads over async tasks.
=============================================
As far as I know at this point, as keyword "await" can only be used in the scope of an "async" function, and only "async" function could be "awaited". But why? And what is it used to, as there is already "async"? Anyway, I tested the code below:
use async_std::{task};
// async fn easy_task() {
// for i in 0..100 {
// dbg!(i);
// }
// println!("finished easy task");
// }
async fn heavy_task(cnt1: i32, cnt2: i32) {
for i in 0..cnt1 {
println!("heavy_task1 cnt:{}", i);
}
println!("heavy task: waiting sub task");
// normal_sub_task(cnt2);
sub_task(cnt2).await;
println!("heavy task: sub task finished");
for i in 0..cnt1 {
println!("heavy_task2 cnt:{}", i);
}
println!("finished heavy task");
}
fn normal_sub_task(cnt: i32) {
println!("normal sub_task: start sub task");
for i in 0..cnt {
println!("normal sub task cnt:{}", i);
}
println!("normal sub_task: finished sub task");
}
async fn sub_task(cnt: i32) {
println!("sub_task: start sub task");
for i in 0..cnt {
println!("sub task cnt:{}", i);
}
println!("sub_task: finished sub task");
}
fn outer_task(cnt: i32) {
for i in 0..cnt {
println!("outer task cnt:{}", i);
}
println!("finished outer task");
}
fn main() {
// let _easy_f = easy_task();
let heavy_f = heavy_task(3000, 500);
let handle = task::spawn(heavy_f);
print!("=================after spawn==============");
outer_task(5000);
// task::join_handle(handle);
task::block_on(handle);
}
the conclusion I got from test is:
1.No matter awaiting async sub_task or just doing normal_sub_task(sync version) in the middle of async heavy_task(), the code below that (the heavy loop task2) would not cut in line.
2.No matter awaiting async sub_task or just doing normal_sub_task(sync version) in the middle of async heavy_task(), the outer_task would sometimes cut in line, breaking the heavy_task1 or async_sub_task/normal_sub_task.
Therefore, what is the meaning of "await", it seems that only keyword "asyc" is used here.
reference:
asyc_std
sing_dance_example from rust asyncbook
module Task in official rust module
recommended article of rust this week about async-programming
another article about rust thread and async-programming using future crates
stackoverflow question:What is the purpose of async/await in Rust?
the conclusion 2 I got seems to be violated against what Shepmaster said, "...we felt async functions should run synchronously to the first await."
The await keyword suspends the execution of an asynchronous function until the awaited future (future.await) produces a value.
It is the same meaning of all the other languages that uses the await concept.
When a future is awaited the "status of execution" of the async function is persisted into an internal
execution context and others async functions have the opportunity to progress if they are ready to run.
When the awaited future completes the async function resumes at the exact point of suspension.
If you think I need only async and write something like:
// OK: let result = future.await
let result = future
You don't get a value but something that represents a value ready in the future.
And if you mark async a function without awaiting anything inside
the body of the function you are injecting into an asynchronous engine a sequential task
that when executed will run to completion as a normal function, preventing asynchronous behavoir.
Some more comments about your code
Probably the confusion arise from a misunderstaning ot the task concept.
When learning async in rust I found the async book pretty useful.
The book define tasks as:
Tasks are the top-level futures that have been submitted to an executor
heavy_task is really the unique task in your example because it is the only future submitted to the async
runtime with task::block_on.
For example, the function outer_task has nothing to do with asynchronous world:
it is not a task, it get excuted immediately when called.
heavy_task behaves asychronously and await
sub_task(cnt2) future ... but sub_task future once executed
goes immediately to completion.
So, as it stand, your code behave practically as sequential.
But keep in mind that things in reality are more subtle, because in presence of other async tasks the
await inside heavy_task works as a suspension point and gives opportunity to other
tasks to be executed toward completion.

Resources