# Concurrency Deft provides three concurrency primitives that share a single per-runtime scheduler. Each is tuned for a different pattern: | Primitive | Use when | |---|---|---| | `deftask` | Run a value-producing computation on a separate thread by default; `@stream` for incremental results. | | `defworker` | Persistent worker or pool — a file loaded in a long-lived child runtime with full module access (see [defworker](defworker)). | | `defcoro` + `spawn` | Cooperative long-lived process with its own event loop. | | `after` / `after-cancel` | One-shot timer. | `defer` (covered in [Control Flow](control-flow)) provides cleanup on function exit and composes well with all three. ## `deftask` — Async Value `deftask` runs its body on a **fresh OS thread** with its own runtime (the closure's bytecode is deep-copied, and global data values it references are snapshotted) and returns a **future**. There is no worker pool — each call spawns a new thread. `await` blocks until the future resolves and yields its value. The body must be self-contained: native stdlib functions are available, but **captured locals and user-defined functions do not cross into the child runtime**. Use literals, global data, and stdlib functions only — a local captured from the enclosing function (`$url`, say) will come through as `nil`. (For full module access — user-defined functions, imports, persistent state — use [defworker](defworker).)
 # Global data referenced by the body is snapshotted into the child runtime.
def api-base "https://api.example.com"

def f1 [deftask { [http/get "$api-base/a"] }]
def f2 [deftask { [http/get "$api-base/b"] }]
set results [await-all @{$f1 $f2}] 
`@parallel` is accepted as an explicit marker for the same behavior:
 def f [deftask { @parallel; [http/get "$api-base/a"] }] 
The mode decorators go at the top of the body — each on its own line, or separated with `;` (`{ @stream body }`, `{ @stream @buffer-size 128 body }`). `await` accepts one future — or several: `[await $f1 $f2 ...]` blocks for each in turn and returns a list of values (in order). For lists of futures, `await-all` is a `stdext/async` wrapper that resolves each in turn and skips non-future entries:
 from "deft/stdext/async" import await-all 
### `@stream` — incremental results For long-running producers, marking the body with `@stream` yields a stream of values rather than one final result. The producer runs on its own OS thread; use `@buffer-size N` to set the stream buffer size (default 64). See [Stdlib](stdlib) for the `task/stream-*` family.
 def s [deftask {
    @stream
    @buffer-size 128
    yield 1
    yield 2
    yield 3
}]
set row [task/stream-next $s]
while ![nil? $row] {
    # ... process $row ...
    set row [task/stream-next $s]
}
# or just: def rows [task/stream-collect $s] 
### Error semantics How a throw inside the task body surfaces depends on the mode: - **`deftask` / `@parallel`** — the future resolves with the error string (e.g. `"boom (line 1)"`), and `await` returns it as the task's value. It does **not** re-throw at the await site. - **`@stream`** — the stream simply ends; the error is swallowed.
 set v [await $f]
if [is? "string" $v] {
    # task body threw: $v is the error message
} 
## `defcoro` — Cooperative Coroutine A coroutine is a long-lived process with its own event loop. Use `spawn` to start one and `vwait` to drive it.
 def state [atom %{ :count 0 }]

defcoro worker {|name^string|
    while ([deref $state]~count < 3) {
        swap! $state { |s| assoc $s :count ($s~count + 1) }
        echo "$name tick"
        yield --wait 500
    }
}

set co [spawn [$worker "alpha"]]
[vwait]                                      # drives all coroutines on this runtime 
### `yield` `yield` suspends the coroutine and returns control to the caller. The optional `--wait ms` flag suspends for at least `ms` milliseconds:
 defcoro ticker {||
    set n 0
    while true {
        incr n
        echo "tick $n"
        yield --wait 1000
    }
} 
### `resume` `resume` wakes a yielded coroutine. The scheduler calls this for you inside `vwait`; you rarely invoke it directly. ### `coro-state` `[coro-state $co]` returns one of `:ready`, `:running`, `:suspended`, `:done`, or `:failed`. ## `after` — One-Shot Timer `after ms fn` schedules `fn` to run after `ms` milliseconds. Returns a timer id you can cancel:
 set id [after 5000 { echo "five seconds elapsed" }]

after-cancel $id                               # cancel the pending timer 
Timers fire on the runtime's event loop — they don't need a separate thread. ## `sleep` `sleep ms` blocks the current coroutine/task for at least `ms` milliseconds, yielding control to other tasks meanwhile. Inside a plain script (no scheduler running) it just blocks.
 sleep 100 
## `defer` — Cleanup on Exit `defer` works inside async code the same way it works in synchronous functions: the block runs when the enclosing function exits, however it exits. This makes it ideal for releasing async resources:
 def with-connection {|url^string|
    set conn [net/connect $url]
    defer { net/close-conn $conn }

    # ... use $conn — it'll be closed whether we return normally,
    # throw, or the task is cancelled ...
    [do-stuff $conn]
} 
See [Control Flow](control-flow) for the full `defer` reference. ## Per-Runtime Scheduler Each runtime has exactly one coroutine scheduler. The `deft` scripting binary drives one runtime; each TUI app panel runs its own runtime in its own OS thread (see [Runtimes](../02-Platform/runtimes)). Consequences: - Coroutines **on the same runtime** share state directly; no locks needed. - `deftask` bodies always run in a **separate child runtime** (the closure is snapshotted, globals are deep-copied) — pass data in through the body and get the result back through the future/stream. - Long-lived runtimes **on different runtimes** must use `pub/*` (see [Pubsub](../02-Platform/pubsub)) or `rpc` (see [RPC](../02-Platform/rpc)) to communicate. ## Decision Matrix | Need | Use | |---|---| | Run a slow operation in the background while the foreground continues | `deftask { ... }` + `await` | | Concurrent map over N inputs | `deftask { ... }` (self-contained bodies) + `await-all` | | Persistent worker with full module access (user fns, imports, state) | `defworker` + `worker/send` | | Long-lived background process | `defcoro` + `spawn` + `vwait` | | One-shot delayed action | `after ms fn` | | Periodic action | `defcoro` with `yield --wait ms` in a loop | | Cross-runtime messaging | `pub/publish` + `pub/subscribe` | | Cross-runtime direct call | `rpc` against `@rpc` functions | ### Why `defworker` over `deftask`? `deftask` snapshots the closure's bytecode + referenced globals into a fresh one-shot runtime: the body must be self-contained (no user-defined function calls, no captured locals), and the worker dies after one job. `defworker` loads a *file* into a persistent child runtime on its own OS thread — the file's full module graph is available, `@worker`-marked defs are its handlers, state persists across jobs, and handler errors resolve to `%Err` without killing the worker. See [defworker](defworker) for the full reference. ## Quick Reference | Form | Purpose | |---|---| | `[deftask { ... }]` | Run body on a fresh OS thread, return a future | | `[deftask { @parallel ... }]` | Explicit form of the default | | `[deftask { @stream ... }]` / `@buffer-size N` | Stream of incremental results | | `[defworker "path.dft"]` / `[defworker "path.dft" n]` | Persistent worker / pool (see [defworker](defworker)) | | `[worker/send $w "name" args...]` | Dispatch a job to a worker; returns a future | | `[worker/stream $w "name" args...]` | Streaming job; `yield` in the handler pushes onto the returned stream | | `[await $future]` | Block for a future's value | | `[await $f1 $f2 ...]` | Block for each future, return a list of values | | `await-all` (from stdext/async) | Block for a list of futures | | `defcoro name { ... }` | Declare a coroutine | | `spawn $coro-or-closure` | Start a coroutine | | `[vwait]` | Drive all coroutines on this runtime | | `yield` / `yield --wait ms` | Suspend | | `[coro-state $co]` | Inspect state | | `[after ms fn]` / `[after-cancel id]` | One-shot timer | | `sleep ms` | Block current task |