Why is this async code not causing CPU spikes?

There is an infinite loop { } declared here, why does it not cause CPU (core) to be blocked at 100% like when using while (true) { }? There is no use of sleep() or equivalents either which could "free" up the CPU.

use async_std::io;
use async_std::task;

async fn async_read_stdin() {
    loop {
        let mut input = String::new();
        let text = io::stdin().read_line(&mut input).await.unwrap();
        println!("You entered: {}", text);

fn main() {
    /// Some other code

Also, how can I get the program to continue with /// some other code while the program waits on async_read_stdin? As in when the some thing arrives on stdin, the context switches to execute that input otherwise will continue with /// some other code. Threads is one way I can think of.

This is because you are awaiting read_line. This creates a suspension point where task::block_on decides to sleep to conserve cpu time.

Thanks! https://docs.rs/async-std/1.0.1/src/async_std/task/block_on.rs.html#127 - is this suspension point you are referring to?

No, in your code you have .await. This is the suspension point. block_on just exploits this to minimize cpu usage.

Thanks, again. Any code reference or short explanation of how it does that?

I'm not sure. async functions are really new, so I don't know any good resources for explaining it.

But this is one of the reasons for using async, to efficiently wait for something, be it a network call, user input or anything else.

1 Like

Use task::spawn instead of block_on.


Your code goes back and forth though the whole asynchronous machinery, but it actually behaves 100% like sync code, because:

  • you .await on every line, so lines are read one by one between print statements, exactly like in sync code. You have no other work to switch to to do in .await while it waits for I/O.
  • you run only one instance of this code, blocking main thread on it.

So despite using async libraries, the code isn't behaving like async code at all.

  • if you remove block_on, or block on join of multiple futures, you will be able to run more things at once and take advantage of some parallelism.

  • just reading and printing lines is probably too simple task to really need clever machinery. If you did something more complex with the lines, it could make sense to read on one thread, and send lines over mpsc channel to another thread to process it. That actually works fine with regular non-async reading and queues, or rayon.

1 Like

Would this be the way to ensure the code is not blocking on the main thread.

fn main() {
    let th = thread::spawn(move || {task::block_on(async_read_stdin())});

I first tried using task::spawn but the program exits immediately with block_on.

You need to call join on the returned JoinHandle to prevent main from completing, just like with threads. You can do this with task::block_on because JoinHandle is a Future

Just to follow-up: Though bit dated (2016) and I am not sure how far has the eventual implementation in 1.39 has aligned with it, this blog post along with the chapter two of the async book really helped in developing some understanding about Rust's async model.