Concurrency and async
G# has two complementary concurrency models: Go-inspired channels and structured go scopes, and CLR-inspired async/await with task and iterator state machines.
go and scope
go call() starts a function call concurrently. The binder requires the operand to be a call expression even though the parser accepts any expression. Use scope to structure child work: child tasks registered inside the scope are joined at scope exit and failures propagate.
scope {
go producer(ch)
go consumer(ch)
}
In the interpreter, go is implemented with Task.Run; outside scope, exceptions can be unobserved. Some interpreter evaluation is serialized, so performance characteristics should be validated in emitted programs. The model is documented by ADR-0002.
Channels
Channels are typed with chan T and created with make(chan T) or make(chan T, capacity). Send uses statement syntax ch <- value; receive uses prefix expression <-ch. Closing a channel is supported. In the current channel path, receiving after close yields the element default value.
package GSharp.Samples.Channels
import System
let ch = make(chan int32, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
let a = <-ch
let b = <-ch
let c = <-ch
let d = <-ch
Console.WriteLine(a)
Console.WriteLine(b)
Console.WriteLine(c)
Console.WriteLine(d)
1
2
3
0
Channel lowering rationale is in ADR-0022.
Select
select waits over channel operations. The implemented case forms are default, receive and discard, receive and bind, and send.
select {
case value := <-ch {
Console.WriteLine(value)
}
default {
Console.WriteLine("nothing ready")
}
}
Use select when readiness determines control flow. Keep case bodies short and delegate larger work to functions.
Async functions and await
async func declarations and literals produce task-returning methods in the emit path. await expr is a prefix expression and is diagnosed outside async contexts or on non-awaitable operands. Async function type clauses are written async func(P) R; spelling an explicit task type as the return in that clause is diagnosed because the async marker already supplies the task shape. Async lowering is covered by ADR-0023.
Sequences and async sequences
A function returning sequence[T] can use yield. async sequence[T] is the async sequence type and is consumed with await for.
func Numbers() sequence[int32] {
yield 1
yield 2
}
Sequence design is described in ADR-0040, ADR-0041, ADR-0042, and ADR-0043.
Cleanup and concurrency
defer and using are scoped constructs. Inside concurrent code, prefer scope plus using or small deferred cleanup calls so lifetimes are obvious. See ADR-0030.