What's the point of async blocking?

Afaik the whole point of async is to be more resource efficient, than threads and independent of the operating system, therefor they are sometimes called lightweight threads. This is achieved by futures, which get polled and tell the executor, when they are ready to make progress, so in the meantime the thread can poll a different future.

Tokio provides some async types, that replace the std counterpart like tokio::fs::File, which returns futures and therefore should be in theory more resource efficient in comparison to simply calling something blocking inside a future?

async {
    File::open("example.txt");
}
// vs
async {
    File::open("example.txt").await;
}

but internally the thread gets blocked, like it'd be with the sync version?, so what's the point of those types? Is there some magic involved in the asyncify function of tokio, that makes then more efficient, than the std version?

I am talking about this function; https://github.com/tokio-rs/tokio/blob/9766cd644fb3a5c2838085f7d40ab37416f2cee8/tokio-fs/src/lib.rs#L81

Yep, that's mostly because of lacking some functionality from kernel, existing aio does not support buffered io, new io_uring is too young right now, but hopefully soon it will become more popular, then tokio will use it as async backed for file io. But even the current implementation may be really useful, it allows you to move all file operations into separate threads and do other job in parallel, and tokio will take care of the thread count (creating only limited amount of them depending of cpu count).

1 Like

Yes, if you look at the tokio::executor::blocking::run function that this ultimately calls into it's quite complex. Basically what this does is it actually runs the blocking code on another thread pool dedicated to running blocking tasks, it then returns a future that will resolve once that thread pool has finished the task and returns a result.

This doesn't really make it more efficient than the std version, but it means the blocking call will not actually block the current async task, so the async executor can go on to service other async tasks while the blocking thread pool runs the blocking code.

1 Like

Luro02,

I think the answer to your question is in the documentation of the function you linked to:

//! Tasks run by worker threads should not block, as this could delay
//! servicing reactor events. Portable filesystem operations are blocking,
//! however. This module offers adapters which use a [blocking] annotation
//! to inform the runtime that a blocking operation is required. When
//! necessary, this allows the runtime to convert the current thread from a
//! worker to a backup thread, where blocking is acceptable.

Which says to me that, yes, we would like all things to be async but that is not always possible, so here is a "adapter" for blocking calls that handles them with threads and presents them as async.

I have never heard of asyn code being "light weight threads", surely that is a different thing?

To my mind, ideally no system or other calls would ever block. Also there would be no polling going on. Polling is not efficient. Rather an event driven, async, system would be driven by events from the operating system, which ultimately derive from interupts.

I do worry about the idea of shoe horning async into a language that basically is not async. So far, to my mind, it makes the code incomprehensble and seems like a kludge. Unlike, say, Javascript where everything is async from the ground up. I presume it's worth the pain for the performance gains.

1 Like

Thanks for the answers, I now understand the point of annotating blocking code.

Would you mind giving some examples for this point?
I can agree, that async blocks cause some clutter, because of the extra indent, but that's the only problem I have with the syntax.

fn call_me() {
    println!("Thanks for calling :)");
}
// vs
async fn call_me() {
    println!("Thanks for calling :)");
}

I don't think you could've done it any better than that...