Async System
Alia natively supports parallel processing, thread control, and asynchronous programming. The core concepts include run/await (thread start/stop), Channel (inter-thread communication), and select (multiplexed selection).
Syntax Overview
run <Stack body> ~ runs a stack in an independent thread, returning a Stack with attached thread state
run <Variable stackVar> ~ starts an existing Stack object
await <Stack stackVal> ~ waits for a parallel stack to finish and returns its execution result; a non-parallel stack reports Stack is not running in parallel
new Channel[<Number cap>] ~ creates a buffered channel
new Channel[] ~ creates an unbuffered channel
<Channel>.send <Value> ~ sends a value; throws when the channel is closed or the buffer is full
<Channel>.trySend <Value> ~ non-blocking send, returns a Boolean
<Channel>.receive ~ receives a value; returns Null when a buffered channel is empty, blocks when an unbuffered channel is empty
<Channel>.tryReceive ~ non-blocking receive, returns Null when empty
<Channel>.close ~ closes the channel
select <Stack body> ~ multiplexed selection; only case receive / case send clauses are allowed in body
case receive <Variable> from <Value> <Stack body> ~ receive branch
case send <Value> to <Value> <Stack body> ~ send branch
Zero-argument channel and stack methods use the call form: call ch.receive, call ch.tryReceive, call ch.close, call t.join, call t.result, call t.detach, call s.run. Properties use direct access: ch.capacity, ch.length, ch.isClosed, t.isRunning, t.isDone, t.isDetached.
Channels
Channel is Alia's built-in thread-safe communication channel, modeled after Go's channel.
Creating channels
const ch = new Channel[5]
const uch = new Channel[]
assert ch.capacity == 5 error 'buffered capacity mismatch'
assert uch.capacity == 0 error 'unbuffered capacity mismatch'
Basic operations
const ch = new Channel[3]
ch.send 'hello'
ch.send 42
assert ch.length == 2 error 'length mismatch after send'
assert (call ch.receive) == 'hello' error 'receive mismatch'
assert (call ch.receive) == 42 error 'receive number mismatch'
assert ch.length == 0 error 'length mismatch after receive'
Non-blocking operations succeed when the buffer is not full or not empty, and return false or Null when the condition is not met, without throwing:
const ch = new Channel[1]
assert ch.trySend 'x' error 'trySend mismatch'
assert (ch.trySend 'y') == false error 'trySend full mismatch'
assert (call ch.tryReceive) == 'x' error 'tryReceive mismatch'
assert (typeOf call ch.tryReceive) == 'Null' error 'empty tryReceive mismatch'
The close operation marks the channel as closed; buffered values can still be received, and sending to a closed channel throws:
const ch = new Channel[2]
ch.send 'a'
call ch.close
assert ch.isClosed error 'should be closed'
assert (call ch.receive) == 'a' error 'buffered receive after close mismatch'
A buffered send throws Channel buffer is full when the buffer is full; receive returns Null when the buffer is empty. An unbuffered receive blocks and waits while the buffer is empty and the channel is not closed; close wakes up waiting receivers.
Properties
| Property | Type | Description |
|---|---|---|
capacity | Number | Buffer capacity (0 for unbuffered) |
length | Number | Number of elements currently in the buffer |
isClosed | Boolean | Whether the channel is closed |
Properties use direct access, without call.
Threads (run / await)
Starting a thread
run places a Stack into an independent thread for parallel execution and returns the same Stack object with thread state attached:
const t = run {
print 'Hello from thread!'
42
}
assert t.isRunning error 'thread should be running'
Worker threads rebuild an independent runtime execution from the stack source and do not share the creating thread's variables or channel buffers. Scenarios that need to pass results back are collected on the main thread via await or call t.join.
Waiting for a thread
await blocks the current thread, waiting for the target thread to finish and returning its execution result:
const t = run { 42 }
const v = await t
assert v == 42 error 'await result mismatch'
assert t.isDone error 'should be done'
The target of await must be a Stack currently running in parallel. Targeting a non-parallel stack reports Stack is not running in parallel. Starting an already running stack again reports Stack is already running in parallel.
const s = { 42 }
assert s.isRunning == false error 'fresh stack should not be running'
assert s.isDone == false error 'fresh stack should not be done'
Thread properties
| Property/Method | Type | Description |
|---|---|---|
isRunning | Boolean | Whether the thread is currently running in parallel; direct access |
isDone | Boolean | Whether execution has finished; direct access |
isDetached | Boolean | Whether the thread is detached; direct access |
join | → Value | Waits for completion and returns the result (blocking); invoked as call t.join |
detach | → Null | Detaches the thread, sets isDetached, and returns Null; invoked as call t.detach |
result | → Value? | Non-blocking result retrieval; returns Null if not finished; invoked as call t.result |
const t = run { 40 + 2 }
assert (call t.join) == 42 error 'join result mismatch'
assert t.isDone error 'should be done after join'
const t = run { 42 }
call t.detach
assert t.isDetached error 'should be detached'
await t
select Multiplexed Selection
The select statement waits across multiple channel operations and executes the first ready branch. Branch readiness is determined based on non-blocking tryReceive/trySend:
const ch1 = new Channel[1]
const ch2 = new Channel[1]
ch1.send 'A'
ch2.send 'B'
let received = ''
select {
case receive v1 from ch1 {
received = v1
}
case receive v2 from ch2 {
received = v2
}
}
assert received == 'A' | received == 'B' error 'select receive mismatch'
When no channel is ready, select executes no branch:
const ch = new Channel[1]
let ran = false
select {
case receive v from ch {
ran = true
}
}
assert ran == false error 'select empty mismatch'
A send branch is ready when the target channel is writable:
const ch1 = new Channel[1]
const ch2 = new Channel[1]
ch1.send 'A'
let out = ''
select {
case receive v from ch1 {
out = v
}
case send 'B' to ch2 {
out = 'sent'
}
}
assert out == 'A' error 'select send branch mismatch'
Complete Example: Worker Pool
Parallel computation executes in independent threads, while channels handle task distribution and result collection on the main thread. The run body does not capture external channels; results cross threads via await back to the main thread before being pushed into the channel:
const t1 = run { 1 + 2 }
const t2 = run { 3 * 4 }
const v1 = await t1
const v2 = await t2
const results = new Channel[10]
results.send v1
results.send v2
call results.close
assert results.length == 2 error 'results length mismatch'
assert (call results.tryReceive) == 3 error 'first result mismatch'
assert (call results.tryReceive) == 12 error 'second result mismatch'
The channel task pipeline executes sequentially on the main thread:
const tasks = new Channel[10]
const orderedResults = new Channel[10]
each i in [1, 2, 3, 4, 5] {
tasks.send i
}
call tasks.close
while tasks.length > 0 {
const item = call tasks.tryReceive
if typeOf item == 'Number' {
const doubled = item * 2
orderedResults.send doubled
}
}
call orderedResults.close
assert orderedResults.length == 5 error 'ordered results mismatch'
Standard Library Async Functions
Some standard library functions return Stacks conforming to Alia's async model (with isRunning/isDone/isDetached properties and join/result/detach methods invoked via call), consumed via await:
io.read/io.readChar/io.readKey: property-style access returns a Stack;call task.joinlazily performs the synchronous read, returningNullon EOFio.readLine/io.readPassword: single-argument calls return a Stack;io.readLine 'prompt'passes in a prompt message, returningNullon EOFweb.httpRequest: single-object-argument call; the request executes on a background thread, and afterawaitthe response object is accessed viatask.response; theawaitexpression retrieves the response in one step
await accepts expressions returning Stacks and retrieves the result in one step; stack literals, parallel stacks, and the async Stacks above all have a typeOf of 'Stack'. The await semantics of web.httpRequest match the tests: after const task = web.httpRequest opts, await task accesses the response object via task.response, while await web.httpRequest opts retrieves the response in one step; the following example only creates the task and checks its Stack shape, without awaiting, to avoid depending on an external service:
import 'web' as web
const opts = *[url: 'http://localhost:19995/get']
const task = web.httpRequest opts
assert task.isRunning error 'http task should be running'
assert (typeOf task) == 'Stack' error 'http task type mismatch'
import 'web' as web
const parsed = web.urlParse 'https://localhost/path'
assert parsed.protocol == 'https' error 'protocol mismatch'
assert parsed.hostname == 'localhost' error 'hostname mismatch'
import 'web' as web
const server = call web.serverCreate
server.close
assert true error 'server close mismatch'
web.serverCreate creates a server object via call; server.close is property-style access that synchronously performs the close when accessed and returns an already-finished Stack. server.listen and addRoute are regular server methods.