4

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."

Dong Robbin
  • 119
  • 1
  • 7
  • If async/await works the same as in C#, then it is not for parallelization but to let the current thread be available to do other work while waiting for some IO task to finish. And you don't have any – Hans Kesting Feb 15 '20 at 13:41
  • Note that nothing in your code is actually async. You even have *blocking* calls (`println!`) and computation in there. You can't just add the `async` keyword to a function to make it asynchronous, it requires more work that this (otherwise it could be completely automated, and we wouldn't need a new keyword). – mcarton Feb 15 '20 at 13:42
  • You need to review the fundamentals of concurrency. Your basic understanding is somewhat inaccurate. – Sebastian Redl Feb 15 '20 at 21:05

1 Answers1

5

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.

attdona
  • 17,196
  • 7
  • 49
  • 60
  • Thanks for your answer. For the last statement about "no await in an async function", I've also heard ago. But just as what I tested,with normal_sub_task in the heavy_task, the outer_task and the heavy_task are really working asychonously. I don't know why. – Dong Robbin Feb 15 '20 at 20:54
  • I've updated the answer with some details. I hope it could help. – attdona Feb 16 '20 at 09:17
  • thanks for your updates. But I am still confused on your words" ...but sub_task future once executed goes immediately to completion.So, as it stand, your code behave practically as sequential." From the 2ed conclusion I got ,which I stated in the question, the sub_task are always interrupted by outer_task, be it async(sub_task.await) or sync version (directly call normal_sub_task). So if the "sequential" you said is about the order inside the heavy_task, I totally agree---as it is always sequential! And the outer_task always interrupt the async heavy_task ..(not finished) – Dong Robbin Feb 17 '20 at 11:34
  • ..at any part. So even I guess the "sequential" you refered to is about the order between the outer_task and the async heavy_task, as I've tested, it is "always" "unsequential"--in detail, the outer_task would interrupt "heavy_task1","heavy_task2"or even " sub_task().await" and "normal_sub_task(cnt2)". Therefore, I got the 2 conclusions, and that is why I think await is meaningless and then ask the question. (Finished) – Dong Robbin Feb 17 '20 at 11:41
  • I think you are still confused because in your mental model `task::spawn(heavy_f)` starts the execution of `heavy_task`. This is not true. `eavy_task` start running after `task::block_on(handle)`. I stress again the fact that `outer_task` is not an async task. – attdona Feb 17 '20 at 12:19
  • thanks for feedback. I think there is something clear now. Asyc programming is only meaningful when there are more than 1 tasks. The async cooperation would only happen between these tasks. But in my example, there is only 1 async task, so it is sequential. And one thing you are wrong is that the `heavy_task` does run immediately after `task::spawn(...)`. – Dong Robbin Mar 05 '20 at 13:28