Concurrency
Grammar
Section titled “Grammar”Await = "await" Unary .AsyncLet = "async" "let" identifier [ ":" Type ] "=" Expr .Yield = "yield" Expr .Spawn = "spawn" Block . // expression → JobTaskFunc = "task" "func" identifier "(" [ ParamList ] ")" [ ReturnType ] Block .Select = "select" "{" { SelectArm } "}" .SelectArm = ".recv" "(" identifier "," Expr ")" Block | ".send" "(" Expr "," Expr ")" Block | ".timeout" "(" Expr ")" Block | "default" Block .chan<T>(n) is a construction expression yielding Chan<T>. await foreach … in … consumes an async
stream. spawn, chan, await are reserved; task and yield are contextual.
Uncolored async
Section titled “Uncolored async”A function is asynchronous iff its body contains await — there is no async keyword, and asyncness does
not propagate up the call chain. The declared return type is the unwrapped value; the awaitable is
generated. The return type selects the builder (“semi-coloring”):
| Declared return | CLR return | Builder |
|---|---|---|
bare T / ValueTask<T> | ValueTask<T> (default) | AsyncValueTaskMethodBuilder<T> |
-> Task<T> / -> Task | Task<T> / Task | AsyncTaskMethodBuilder<T> |
-> void (explicit) | void (async-void) | AsyncVoidMethodBuilder |
-> IAsyncEnumerable<T> + yield | IAsyncEnumerable<T> | channel-backed |
The body is identical across the first three; only the wrapper changes, and the call site never does.
Explicit -> void is async-void (event handlers) and carries the unobserved-exception caveat.
Async streams
Section titled “Async streams”A function declared -> IAsyncEnumerable<T> whose body uses yield is an async stream; it may interleave
yield e (produce) and await (suspend), and is consumed with await foreach. The lowering is
channel-backed (a capacity-1 Chan<T> plus a producer task): it gives backpressure, cancellation on early
consumer exit, and producer-exception propagation, and runs one element ahead (not a pure lazy pull). A
yield outside such a function is ES2131.
async let
Section titled “async let”async let name = init starts init concurrently at the declaration; the first textual reference awaits
it (Swift-style fan-out, written against the unwrapped type). The initializer shall be a call or awaitable
(ES3005); a sync user-function initializer is auto-wrapped in Task.Run with
ES3004. Lowering happens before either backend sees the tree.
spawn / task func
Section titled “spawn / task func”spawn { … } runs a block as a job, yielding a Job. task func f() makes the call site a spawn:
calling f() runs the body as a task and returns Job / Job<T>. A function literal inside a task func
body shall not capture a var from the surrounding scope (ES2130); thread
shared state through chan<T> parameters. let captures are permitted.
Channels and select
Section titled “Channels and select”chan<T>(n) constructs a bounded channel (unbounded if n omitted), backed by
System.Threading.Channels. for v in ch drains until the channel completes. select waits on multiple
channel operations: a ready .recv/.send arm fires (fairness shuffle across ready arms); default is
held back on the non-blocking pass so a ready operation always wins; without default, select blocks
until an operation completes or a .timeout elapses. Inside a spawn body the job’s cancellation token is
threaded into select and channel iteration, so a cancelled task unwinds in bounded time.
Structured concurrency — TaskScope
Section titled “Structured concurrency — TaskScope”TaskScope owns child tasks and channels, propagates cancellation in and out, and does not exit until
every child completes; a child’s first exception cancels its siblings and is rethrown on exit.
Stdlib types
Section titled “Stdlib types”| Type | Key members |
|---|---|
Job | Cancel() · Wait() · WaitAsync() -> ValueTask · AsTask() |
Job<T> | Wait() -> T · WaitAsync() -> ValueTask<T> · AsTask() · Cancel() |
Chan<T> | Send · SendAsync · ReceiveAsync · TrySend · TryReceive · Complete/Close · for..in / await foreach |
TaskScope | RunAsync(...) · Spawn(ct => …) · Token · Chan<T>(n) (auto-completed) · Defer(() => …) |
Prefer scope.Chan<T>(n) for auto-completion on scope exit. The language-level spawn { } currently
lowers to Job.Spawn; a scope-aware lowering is future work.