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.
VOID Language threading
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
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.
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.
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.
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.
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.ThreadThe 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.
ThreadPoolA 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.
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 schedulingOrdinary 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.
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 & ValueTaskTask.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.
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.
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.
Task.DelayOne 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.
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.WaitAsyncCancellation 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 / ExitManaged 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 / PulseAllCondition 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.
Thread.SleepVOID 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.
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.
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.InterlockedInteger 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.VolatileVolatile.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 statementThe 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.
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.
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.
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);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();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);
}using Void;
using Void.Threading;
Thread worker = new Thread(() =>
{
Thread.Sleep(10);
Console.WriteLine("awake");
});
worker.Start();
worker.Join();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();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);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
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.