Async/Await
Concurrent programming with a thread pool backend.
Overview
Strada provides first-class async/await support, allowing you to write concurrent code that's easy to read and maintain. Async functions execute on a thread pool, enabling parallel operations without the complexity of manual thread management.
Defining Async Functions
Use async func to define functions that run asynchronously:
async func fetch_data(str $url) str {
# This runs in the thread pool
my str $response = http_get($url);
return $response;
}
async func compute(int $n) int {
core::usleep(50000); # 50ms work
return $n * 2;
}
When called, async functions immediately return a Future while the actual work executes on a background thread.
Awaiting Futures
Use await to block until a Future completes and get its result:
my scalar $future = compute(21);
say("Future created, working...");
my int $result = await $future;
say("Result: " . $result); # Result: 42
Parallel Execution
Launch multiple async operations concurrently by calling async functions before awaiting:
func main() int {
# Start three operations in parallel
my scalar $a = compute(10);
my scalar $b = compute(20);
my scalar $c = compute(30);
# Wait for all results
# Total time ~50ms, not 150ms!
my int $r1 = await $a; # 20
my int $r2 = await $b; # 40
my int $r3 = await $c; # 60
return 0;
}
async:: Namespace Functions
| Function | Description |
|---|---|
async::all(\@futures) |
Wait for all futures, return array of results |
async::race(\@futures) |
Wait for first to complete, cancel others |
async::timeout($f, $ms) |
Await with timeout (throws on timeout) |
async::cancel($f) |
Request cancellation of a future |
async::is_done($f) |
Non-blocking completion check |
async::is_cancelled($f) |
Check if future was cancelled |
async::pool_init($n) |
Initialize thread pool with N workers |
async::pool_shutdown() |
Shutdown thread pool |
Wait for All (async::all)
Wait for multiple futures to complete and get all results as an array:
my array @futures = (compute(1), compute(2), compute(3));
my array @results = async::all(\@futures);
# @results is [2, 4, 6]
Race (async::race)
Wait for the first future to complete, automatically cancelling the others:
async func slow_task(int $id, int $delay_ms) str {
core::usleep($delay_ms * 1000);
return "task " . $id;
}
my array @futures = (
slow_task(1, 100), # 100ms
slow_task(2, 50), # 50ms - wins!
slow_task(3, 150) # 150ms
);
my str $winner = async::race(\@futures);
say($winner); # "task 2"
Timeout
Set a deadline for an operation:
my scalar $slow = slow_task(99, 500); # 500ms
try {
my str $r = async::timeout($slow, 100); # 100ms timeout
say("Got: " . $r);
} catch ($e) {
say("Timed out: " . $e);
}
Cancellation
Request cancellation of a running future:
my scalar $future = slow_task(1, 1000);
async::cancel($future);
if (async::is_cancelled($future)) {
say("Cancelled!");
}
try {
await $future; # Throws "Future was cancelled"
} catch ($e) {
say("Caught: " . $e);
}
Error Propagation
Exceptions thrown in async functions propagate through await:
async func fail_async() int {
throw "async error";
}
try {
my int $x = await fail_async();
} catch ($e) {
say("Caught: " . $e); # "Caught: async error"
}
Thread Pool Configuration
The thread pool auto-initializes with 4 workers on first async call. For custom configuration:
func main() int {
async::pool_init(8); # 8 worker threads
# ... async operations ...
async::pool_shutdown(); # Optional cleanup
return 0;
}
Non-Blocking Check
Check if a future is complete without blocking:
my scalar $future = compute(42);
my int $polls = 0;
while (async::is_done($future) == 0) {
$polls++;
core::usleep(1000); # 1ms
}
my int $result = await $future;
say("Polled " . $polls . " times, result: " . $result);
Complete Example
async func compute(int $n) int {
core::usleep(50000);
return $n * 2;
}
async func slow_task(int $id, int $delay_ms) str {
core::usleep($delay_ms * 1000);
return "task " . $id . " done";
}
func main() int {
say("=== Async Test ===");
# Basic await
my scalar $f = compute(21);
my int $r = await $f;
say("Result: " . $r); # 42
# Parallel execution
my scalar $a = compute(10);
my scalar $b = compute(20);
say("Results: " . await $a . ", " . await $b);
# Wait for all
my array @futures = (compute(1), compute(2));
my array @results = async::all(\@futures);
say("All: " . join(", ", @results));
# Race
my array @race = (slow_task(1, 100), slow_task(2, 50));
say("Winner: " . async::race(\@race));
return 0;
}
Channels
Channels provide thread-safe message passing between async tasks.
# Create channels
my scalar $ch = async::channel(); # Unbounded
my scalar $ch = async::channel(10); # Bounded (capacity 10)
# Send and receive
async::send($ch, $value); # Blocks if full
my scalar $v = async::recv($ch); # Blocks if empty
# Non-blocking variants
async::try_send($ch, $value); # Returns 0/1
my scalar $v = async::try_recv($ch); # Returns undef if empty
# Close and check
async::close($ch);
async::is_closed($ch);
async::len($ch); # Items in queue
Channel Functions:
| Function | Description |
|---|---|
async::channel() | Create unbounded channel |
async::channel($n) | Create bounded channel |
async::send($ch, $v) | Send (blocks if full) |
async::recv($ch) | Receive (blocks if empty) |
async::try_send($ch, $v) | Non-blocking send |
async::try_recv($ch) | Non-blocking receive |
async::close($ch) | Close channel |
async::is_closed($ch) | Check if closed |
async::len($ch) | Get queue length |
Mutexes
Mutexes protect critical sections from concurrent access.
my scalar $m = async::mutex();
async::lock($m); # Acquire (blocking)
# ... critical section ...
async::unlock($m); # Release
async::try_lock($m); # Non-blocking (0=success)
async::mutex_destroy($m); # Clean up
Mutex Functions:
| Function | Description |
|---|---|
async::mutex() | Create mutex |
async::lock($m) | Acquire lock |
async::unlock($m) | Release lock |
async::try_lock($m) | Non-blocking lock |
async::mutex_destroy($m) | Destroy mutex |
Atomics
Lock-free integer operations for counters and flags.
my scalar $a = async::atomic(0); # Create
async::atomic_load($a); # Read
async::atomic_store($a, 100); # Write
async::atomic_add($a, 10); # Add, returns OLD
async::atomic_sub($a, 5); # Sub, returns OLD
async::atomic_inc($a); # Inc, returns NEW
async::atomic_dec($a); # Dec, returns NEW
async::atomic_cas($a, $exp, $new); # CAS (returns 1 if swapped)
Atomic Functions:
| Function | Description |
|---|---|
async::atomic($n) | Create atomic |
async::atomic_load($a) | Read value |
async::atomic_store($a, $v) | Write value |
async::atomic_add($a, $d) | Add, return OLD |
async::atomic_sub($a, $d) | Subtract, return OLD |
async::atomic_inc($a) | Increment, return NEW |
async::atomic_dec($a) | Decrement, return NEW |
async::atomic_cas($a, $e, $n) | Compare-and-swap |