What's the use case for io.async?

I’d really like to align zio with the future std Io, but I have a problem with the terminology.

There is io.async and then there is io.concurrent, both might spawn the function in some kind of background worker, but io.async might not. I understand the rationale, you want to support single-threaded blocking backend where io.async is a direct function call. However, when would you actually want to use io.async? If you are fine with the code executing serially, why would you not just call it as a function? I can’t imagine a single use case for that.

I’d much prefer if we have io.spawn that explicitly runs the code in some kind of background executor. You could see that as gevent in Python, where without gevent, spawn would run in a thread, with gevent, it would run in a coroutine.

There is also the need for io.spawnBlocking, for functions that are not cooperative, but that could be a backend-specific extension.

However, my main question is, what’s one good use case for io.async?

If the program runs in single-threaded blocking mode async is forced to just call the function, however otherwise you still want to run the asynchronous code asyncronously. (not everyone wants to run their program in blocking mode and the code shouldn’t need to be changed to run it in non-blocking mode)

If you always just use function calls (and this code is part of a library) then the user doesn’t have the option to run it asynchronous.

io.async allows that, but if there is no possibility to have a background executor, you can still run the code in single threaded blocking mode, which is slower but enables more use cases. Giving more options to the user.

io.async enables implementation agnostic expression of the actual asyncrony and concurrency (via io.asyncConcurrent), allowing users to write the code once and use it with multiple different io implementations.

2 Likes

That’s io.concurrent.

When it would be nice to run a function in a concurrent task, but it’s not necessary for correctness. Note also that io.async cannot fail, while io.concurrent can, and you’ll have to handle the failure.

1 Like

I know about the difference, but do you see some use case for io.async?

Let’s say I have a library that handles a lot of data, and multiple streams too. It has a higher level API for the most common use case, which is where I will focus, as an example of async in the library.
It’s perfectly fine to process streams one at a time, but it would be a lot faster to do them concurrently.

io.async expresses exactly that, it allows the caller to choose, via the Io implementation, if the processing is concurrent or not, and even the model of concurrency used.

5 Likes
  • Sending/receiving/parsing multiple DNS queries in parallel
  • Parsing /etc/resolv.conf and /etc/hosts at the same time
  • Hashing N different files independently, before hashing the hashes together to compute package hash
  • Parsing N different .zig files into ZIR independently
  • Compiling N different function’s high level IR into machine code
  • Scanning music files for loudness detection and calculating Acoustid
  • Fetching MusicBrainz data and cover art query simultaneously

In all of these cases, there are tasks that can be done serially but benefit from being done concurrently.

11 Likes

Is it a correct mental model to have that io.async means something can happen concurrently and io.concurrent means that it must happen concurrently?

5 Likes

That is an accurate mental model

1 Like

Even better, it’s a factually true statement.

io.async can decide to allocate a unit of concurrency (thread, green thread, coroutine) to run the provided function concurrently or just call the function immediately (e.g. if resource limits were hit).

io.concurrent instead has to guarantee that the provided function will be placed in a different thread / green thread / coroutine.

io.async can be implemented basically as return io.concurrent(f, args) catch f(args); (slightly simplified)

7 Likes