16. Concurrency
A program runs on one thread, with cooperative concurrency through await. I/O in std/io blocks. A child process started with spawn_async (std/process) runs while the program awaits it.
async/await#
A function that uses await is an async function.
- It declares
-> Async<T>(Async<void>for no value), and its body returns aT. - A call to it gives an
Async<T>, andawait ewaits foreto complete and gives itsT. - A function that returns an
Async<T>it builds, without awaiting, is an ordinary function, assleep(ms) -> Async<null>(std/async) is.
import "std/io"
import "std/async"
mk :: fn(n: i32) -> Async<i32> {
await sleep(i64(n))
return n * 10
}
main :: fn() -> i32 {
rs :: await join_all([mk(3), mk(1), mk(2)])
for r in rs {
println("${r}") // 30, 10, 20: input order
}
return 0
}
await is an error in a nested lambda, a defer body and, for now, the right operand of &&, || or ??, a loop condition or a match guard.
Operands evaluate left to right even when a later one awaits: in f(a(), await b()), a() runs first. An assignment evaluates its target's operands (receiver, index), then its value; a compound assignment place op= e reads place only at the write.
defer works in an async function: the function's pending defers, those registered before an await included, run in reverse order when the function completes or is cancelled.
Running async code#
mainmayawaitand keep itsi32return type; the program runs it to completion.- When every task is suspended, the program waits for the nearest deadline, and stops on a deadlock when none is pending. Without
std/async, amainwhoseawaitsuspends stops the program. sleep(ms)suspends the async function until its deadline: in real time in a native program, while undervader runthe scheduler's clock jumps to it.block_on(x)(std/async) runs an awaitable to completion from a function that is not async.
Combinators#
std/async combines awaitables:
join_all(xs)gives every result, in input order.try_join_all(xs)is the fallible join, giving the first error in input order.race(xs)completes with the first awaitable to settle, value or error.join_any(xs)completes with the first success, or the last error when all fail.timeout(ms, x)gives aTimeoutErrorwhen the deadline passes beforexcompletes.share(x)lets several awaiters awaitx: the firstresumedrives it, and once it completes every later one returns its result (a finished async function answersSuspended). Cancelling a shared awaitable leavesxto the others;xawaiting its own shared awaitable stops the program.
Cancellation#
race, join_any and timeout cancel the awaitables that lose, and nothing else cancels; only a suspended async function can be cancelled. It runs its pending defers in reverse order and cancels the awaitable it waits on, without finishing its body. Structured concurrency (scope, spawn) is planned.
Hand-written awaitables#
An awaitable is a value whose type implements Async<T> (std/core). Its resume returns the T once complete, or a Suspended while it cannot progress; one that holds resources overrides cancel to release them.
Suspended { .wake_at = t }: no progress before the scheduler clock (sched_now, in milliseconds) reachest.Suspended {}(wake_at0): the time is unknown.- An async function reports the
wake_atof the awaitable it waits on.
join_all, race and join_any resume an awaitable only once its wake_at has passed.
Later: coroutines#
Continuation-passing coroutines are planned.
Native threads#
Native threads are planned after the MVP, in std/thread, whose import will be a compile-time error on the WebAssembly targets.