Slide 35 of 74

Concurrency: async

Starting work and waiting for it
proc slow(label: Str, ms: Int): Str {
	hive.task.sleep(ms)
	return label
}

proc main(): void {
	a := async slow("a", 30)      // starts now
	b := async slow("b", 30)      // starts now, alongside
	echo "{a} and {b}"             // waits for both, right here: about 30ms, not 60
}
Fire and forget
proc note(text: Str): void {
	hive.task.sleep(20)
	echo text
}

proc main(): void {
	async note("from another thread")
	echo "main carries on"
	hive.task.sleep(50)         // without this, main would return first
}                               // and take the other thread with it

Every call waits for its callee by default — there is no async func and no future type. What a call means is decided where it is written:

  • f(x) waits for it.
  • async f(x), as a statement, starts it on a thread of its own and carries on without it.
  • x := async f(x) starts it too, and waits only where x is read — the first read waits, and every later one is free. A binding nobody reads still runs.

Whichever starts it, a thread is handed a copy of every argument, worked out on the caller's side first — so async save(build()) runs build() before it carries on, and nothing the caller does afterwards can change what the thread was given. async is only a statement or the value of a declaration; the binding cannot be mut, and what it starts has to be a call to a callable of your own or of the library — not a builtin, a constructor or a partial application, which do no work worth a thread. A call that answers with nothing cannot be bound this way, since there would be nothing for the name to hold.

A panic on a thread started with a binding is raised where the binding is read; one on a thread nobody waits for stops the whole program. When main returns, the program ends, and any thread still running ends with it — except that a program with a window open waits for its windows. hive.task.sleep(ms) parks only the calling thread, and hive.task.sleepForever() parks it for good.