VOID Language threading

Concurrency built into the runtime.

VOID treats threads, scheduling, Task completion, blocking, cancellation, and synchronization as parts of one managed execution model. ThreadPool workers participate in GC and exception rules just like ordinary managed threads, so higher-level work does not escape the runtime underneath it.

Managed concurrency foundation

Threads, Tasks, waits, and scheduling share one runtime model.

The important pieces share state and rules instead of living in separate subsystems. Blocking workers enter GC-safe states, ThreadPool jobs use normal managed delegates, Task completion carries faults and cancellation through existing runtime contracts, and atomics keep cross-thread publication explicit. POSIX and Windows details stay behind VOIDC's runtime boundary.

Portable native thread backend

VOIDC owns the create/join and synchronization abstraction. POSIX pthreads and the Windows thread API stay isolated behind the runtime layer, including build/link integration and lifecycle setup.

Per-thread execution context

Each attached managed thread owns its automatic GC roots, active exception-handler state, native-boundary bookkeeping, native-call state, and related execution data. The main thread uses the same attachment model.

Thread-safe heap registry

Managed allocation metadata and the attached-thread registry are synchronized independently, so worker allocation and runtime bookkeeping are safe without serializing all generated VOID execution behind one global execution lock.

Stop-the-world GC

Collection requests cooperatively park managed workers at compiler/runtime safepoints, scan roots from every registered thread, account for native-quiescent blocking states, sweep once, and resume participating threads.

Void.Threading.Thread

The managed surface uses new Thread(Action), Start(), and Join(). Closures, generic delegate adapters, multicast delegates, nested workers, managed captures, and forced collection use normal language mechanisms.

Managed ThreadPool

A reusable work queue and persistent managed workers sit on the same Thread, monitor, wait, atomic, exception, and GC machinery as direct worker threads. Queued delegates stay rooted correctly while waiting and executing.

Task & Task<T>

Tasks provide synchronized completion state for pending, running, completed, faulted, and canceled work. Generic Tasks carry typed managed results without creating a separate generic runtime or lifetime model.

Generalized await execution

Compiler-generated async methods use one structural awaiter contract for Task, custom awaitables, and ValueTask. Task participates through TaskAwaiter, while continuation registration still targets the existing Task/ThreadPool runtime instead of a separate scheduler.

Task.Run scheduling

Ordinary and async-delegate work can be scheduled through the managed ThreadPool, including closures, generic work, nested scheduling, and worker-to-worker execution. Task-returning delegates are unwrapped through continuation-driven completion so Task.Run(Func<Task>) produces one Task without blocking a worker on the inner Task.

Task waiting, faults & cancellation

Synchronous Task observation reuses VOID's timed-wait and exception machinery. Faults remain managed exceptions, while cooperative cancellation races safely with queued work, execution, completion, timeouts, and disposal.

Task.Yield & ValueTask

Task.Yield() schedules resumption through the existing ThreadPool without manufacturing a Task for the yield point. ValueTask/ValueTask<T> add inline completed paths and Task-backed asynchronous paths while keeping the same await, fault, cancellation, and GC contracts.

Continuations & completion sources

Race-safe Task continuations can register before or after completion, and TaskCompletionSource can complete, fault, or cancel Tasks while keeping captured delegates and managed results alive through normal GC rules.

Task composition

Completion factories create already-terminal Tasks, while Task.WhenAll and Task.WhenAny compose existing Tasks without blocking workers. Aggregation, first-completion races, typed results, faults, cancellation, and managed references all reuse the existing continuation and completion machinery.

Shared timer queue & Task.Delay

One runtime-owned monotonic timer service schedules multiple cancellable deadlines without sleeping ThreadPool workers or creating a thread per timer. Task.Delay completes through the existing Task completion machinery, including zero, infinite, cancellation, GC rooting, and timer-vs-cancel races.

Delayed & linked cancellation

CancellationTokenSource now owns its external and timer registrations deterministically. CancelAfter uses the shared timer queue, repeated deadlines replace correctly, linked sources propagate parent cancellation through normal registrations, and disposal unregisters retained parent/timer state.

Task.WaitAsync

Cancellation and timeout overloads wait nonblockingly through continuations, token registrations, and the shared timer service. The proxy propagates the antecedent when it wins, produces normal cancellation or TimeoutException when those win, and leaves the antecedent Task untouched.

Monitor.Enter / Exit

Managed objects can own recursive monitors through stable non-moving GC allocation identity. Contending enters are GC-aware while blocked, and wrong-thread, unmatched, and null operations fail deterministically.

Monitor.Wait / Pulse / PulseAll

Condition waiting requires monitor ownership, fully releases recursive ownership while blocked, remains GC-safe, and reacquires the exact recursion depth before returning. The timed overload Monitor.Wait(object, int millisecondsTimeout) returns true when notified and false on timeout. Monitor waiting intentionally remains cancellation-free.

Monotonic timing & Thread.Sleep

VOID owns one monotonic deadline/timed-wait layer across POSIX and Windows. Thread.Sleep(int millisecondsTimeout) uses that runtime path, including zero and infinite timeout behavior, while sleeping managed workers remain visible to GC coordination.

Reset events & semaphore

ManualResetEvent, AutoResetEvent, and bounded Semaphore provide indefinite and timed waiting on the existing synchronization runtime. Manual reset keeps its signal until reset, auto reset releases at most one waiter per signal, and semaphore counts remain bounded and race-safe.

Cooperative cancellation

CancellationToken and CancellationTokenSource provide thread-safe, idempotent cancellation with race-safe callback registration and deterministic source disposal. Cancellable event/semaphore waits wake without polling, while delayed and linked cancellation reuse the same registration model instead of adding polling loops.

Void.Threading.Interlocked

Integer increment, decrement, add, exchange, and compare-exchange operations use true runtime-owned atomics. Managed-reference exchange/compare-exchange also participates in VOID's rooting and stop-the-world GC model instead of bypassing managed lifetime rules.

Void.Threading.Volatile

Volatile.Read uses acquire semantics and Volatile.Write uses release semantics for supported primitive and managed-reference values, giving cross-thread publication explicit memory-order behavior without a field-level volatile modifier.

lock statement

The lock expression is evaluated exactly once and rooted for the full critical section. Release uses VOIDC's structured cleanup stack, so normal fallthrough, return, break, continue, and exceptions cannot skip monitor exit.

Worker exception handoff

An unhandled managed exception leaving a worker is preserved as a managed root and rethrown by Thread.Join() on the joining thread after the native join and worker-state retirement complete.

Cross-feature integration

A worker can capture managed state, block, allocate, throw, call native code, and trigger collection without switching to a second lifetime or exception model. The same runtime rules keep those paths connected.

Structured synchronization

lock is cleanup, not a raw mutex shortcut.

The compiler lowers lock through the existing structured-finally transfer machinery. Monitor ownership therefore follows the same control-flow rules already used by exceptions and deterministic disposal instead of depending on generated jumps that could strand a lock.

Managed worker lifecycle
using Void;
using Void.Threading;

public sealed class State
{
    public int Value;
}

State state = new State();
Thread worker = new Thread(() =>
{
    state.Value = 42;
});

worker.Start();
worker.Join();
Console.WriteLine(state.Value);
Structured locking
using Void;
using Void.Threading;

public sealed class Counter
{
    public int Value;
}

Counter counter = new Counter();

Thread worker = new Thread(() =>
{
    lock (counter)
    {
        counter.Value++;
        GC.Collect();
    }
});

worker.Start();
worker.Join();
Timed monitor wait
using Void;
using Void.Threading;

public sealed class Gate
{
    public bool Ready;
}

Gate gate = new Gate();
lock (gate)
{
    bool notified = Monitor.Wait(gate, 25);
    Console.WriteLine(notified);
}
Managed sleep
using Void;
using Void.Threading;

Thread worker = new Thread(() =>
{
    Thread.Sleep(10);
    Console.WriteLine("awake");
});

worker.Start();
worker.Join();
Condition waiting
using Void;
using Void.Threading;

public sealed class Gate
{
    public bool Ready;
}

Gate gate = new Gate();
Thread worker = new Thread(() =>
{
    lock (gate)
    {
        while (!gate.Ready)
            Monitor.Wait(gate);
    }
});

worker.Start();
lock (gate)
{
    gate.Ready = true;
    Monitor.PulseAll(gate);
}
worker.Join();
Atomic update + publication
using Void;
using Void.Threading;

int completed = 0;
int published = 0;

Thread worker = new Thread(() =>
{
    Interlocked.Increment(ref completed);
    Volatile.Write(ref published, 1);
});

worker.Start();
worker.Join();

Console.WriteLine(Volatile.Read(ref published) == 1);
Console.WriteLine(completed == 1);
Worker exception delivery
using Void;
using Void.Threading;

Thread worker = new Thread(() =>
{
    throw new Exception("worker failed");
});

worker.Start();

try
{
    worker.Join();
}
catch (Exception error)
{
    Console.WriteLine(error.Message);
}

Above the runtime

Async remains a consumer of the scheduler, not a replacement for it.

Generalized await, ValueTask, async lambdas, await using, await foreach, async iterators, Task composition, Task.Delay, delayed/linked cancellation, and Task.WaitAsync all reuse the ThreadPool, continuation, timer, cancellation, fault, and GC machinery described on this page. The timer service triggers ordinary Task completion rather than creating a second async state machine, and timeout/cancellation waiting never mutates the antecedent Task.