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

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.

Related

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

Hi, is there is a better way to wait for an async execution to finish in rust

Just to explain, I send a command and an id to the other thread, the other thread executes the thing, and return the command and id as a tuple. Because multiple functions might be called at nearly the same time and they are io operations, they might be returned in a different order than they came. So, I have this function to wait for the right return value and put the rest into a map(the variable called results) so that the other functions calling can retrieve theirs as well. However, I think if there is a case of two functions calling this one at the same time, they might be racing to retrieve the value at the channels. I just think there should be a more elegant way of doing this.
For now I have this function to wait for an async execution in another thread:
async fn wait_for(&mut self, id : u64) -> (u64, io::Result<SomeResult>) {
loop {
let temp = self.channels.1.recv().await.unwrap();
if temp.0 == id {
return temp
} else {
self.results.insert(temp.0, temp.1);
}
if let Some(res) = self.results.remove(&id) {
return (id, res)
}
}
}
P.S. I also had a thought of having another thread do the checking and return the results in order, but that pretty much defeats the purpose of async/await (as far as I know).

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

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;

Does rust currently have a library implement function similar to JavaScript's setTimeout and setInterval?

Does rust currently have a library implement function similar to JavaScript's setTimeout and setInverval?, that is, a library that can call multiple setTimeout and setInterval to implement management of multiple tasks at the same time.
I feel that tokio is not particularly convenient to use. I imagine it to be used like this:
fn callback1() {
println!("callback1");
}
fn callback2() {
println!("callback2");
}
set_interval(callback1, 10);
set_interval(callback1, 20);
set_timeout(callback1, 30);
Of course, I can simulate a function to make it work:
// just for test, not what I wanted at all
type rust_listener_callback = fn();
fn set_interval(func: rust_listener_callback, duration: i32) {
func()
}
fn set_timeout(func: rust_listener_callback, duration: i32) {
func();
}
If a set_interval is implemented in this way, multiple combinations, dynamic addition and deletion, and cancellation are not particularly convenient:
use tokio::time;
async fn set_interval(func: rust_listener_callback, duration: u64) {
let mut interval = time::interval(Duration::from_millis(duration));
tokio::spawn(async move {
loop {
interval.tick().await;
func()
}
}).await;
}
// emm maybe loop can be removed, just a sample
While, What I want to know is if there is a library to do this, instead of writing it myself.
I have some idea if I would write it myself. Generally, all functions are turned into a task queue or task tree, and then tokio::time::delay_for can be used to execute them one by one, but the details are actually more complicated.
However, I think that this general capability may have already been implemented but I has not found for the time being, so I want to ask here, Thank you very much.
And importantly, I hope it can support single thread
setTimeout can be done like this without the need for a crate:
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(5)).await;
// code goes here
});
I asked myself the same question a few days ago, created a solution for this (for tokio runtimes), and found your stackoverflow post just now.
https://crates.io/crates/tokio-js-set-interval
code.rs
use std::time::Duration;
use tokio_js_set_interval::{set_interval, set_timeout};
#[tokio::main]
async fn main() {
println!("hello1");
set_timeout!(println!("hello2"), 0);
println!("hello3");
set_timeout!(println!("hello4"), 0);
println!("hello5");
// give enough time before tokios runtime exits
tokio::time::sleep(Duration::from_millis(1)).await;
}
But this must be used with caution. There is no guarantee that the futures will be executed (because tokios runtime must run long enough). Use it only:
for educational purposes,
and if you have low priority background tasks that you don't expect to get executed always
I created a library just for this which allows setting many timeouts using only 1 tokio task (instead of spawning a new task for each timeout) which provides better performance and lower memory usage.
The library also supports cancelling timeouts, and provides some ways to optimize the performance of the timeouts.
Check it out:
https://crates.io/crates/set_timeout
Usage example:
#[tokio::main]
async fn main() {
let scheduler = TimeoutScheduler::new(None);
// schedule a future which will run after 1.234 seconds from now.
scheduler.set_timeout(Duration::from_secs_f32(1.234), async move {
println!("It works!");
});
// make sure that the main task doesn't end before the timeout is executed, because if the main
// task returns the runtime stops running.
tokio::time::sleep(Duration::from_secs(2)).await;
}

How to freeze a thread and notify it from another?

I need to pause the current thread in Rust and notify it from another thread. In Java I would write:
synchronized(myThread) {
myThread.wait();
}
and from the second thread (to resume main thread):
synchronized(myThread){
myThread.notify();
}
Is is possible to do the same in Rust?
Using a channel that sends type () is probably easiest:
use std::sync::mpsc::channel;
use std::thread;
let (tx,rx) = channel();
// Spawn your worker thread, giving it `send` and whatever else it needs
thread::spawn(move|| {
// Do whatever
tx.send(()).expect("Could not send signal on channel.");
// Continue
});
// Do whatever
rx.recv().expect("Could not receive from channel.");
// Continue working
The () type is because it's effectively zero-information, which means it's pretty clear you're only using it as a signal. The fact that it's size zero means it's also potentially faster in some scenarios (but realistically probably not any faster than a normal machine word write).
If you just need to notify the program that a thread is done, you can grab its join guard and wait for it to join.
let guard = thread::spawn( ... ); // This will automatically join when finished computing
guard.join().expect("Could not join thread");
You can use std::thread::park() and std::thread::Thread::unpark() to achieve this.
In the thread you want to wait,
fn worker_thread() {
std::thread::park();
}
in the controlling thread, which has a thread handle already,
fn main_thread(worker_thread: std::thread::Thread) {
worker_thread.unpark();
}
Note that the parking thread can wake up spuriously, which means the thread can sometimes wake up without the any other threads calling unpark on it. You should prepare for this situation in your code, or use something like std::sync::mpsc::channel that is suggested in the accepted answer.
There are multiple ways to achieve this in Rust.
The underlying model in Java is that each object contains both a mutex and a condition variable, if I remember correctly. So using a mutex and condition variable would work...
... however, I would personally switch to using a channel instead:
the "waiting" thread has the receiving end of the channel, and waits for it
the "notifying" thread has the sending end of the channel, and sends a message
It is easier to manipulate than a condition variable, notably because there is no risk to accidentally use a different mutex when locking the variable.
The std::sync::mpsc has two channels (asynchronous and synchronous) depending on your needs. Here, the asynchronous one matches more closely: std::sync::mpsc::channel.
There is a monitor crate that provides this functionality by combining Mutex with Condvar in a convenience structure.
(Full disclosure: I am the author.)
Briefly, it can be used like this:
let mon = Arc::new(Monitor::new(false));
{
let mon = mon.clone();
let _ = thread::spawn(move || {
thread::sleep(Duration::from_millis(1000));
mon.with_lock(|mut done| { // done is a monitor::MonitorGuard<bool>
*done = true;
done.notify_one();
});
});
}
mon.with_lock(|mut done| {
while !*done {
done.wait();
}
println!("finished waiting");
});
Here, mon.with_lock(...) is semantically equivalent to Java's synchronized(mon) {...}.

Resources