SynchronizationContext

The SynchronizationContext system replaces Unity's default UnitySynchronizationContext with a custom implementation that provides frame-phase-aware async/await execution, cooperative cancellation for tasks, and editor-mode async support. It is the foundation that makes async/await patterns work correctly across Update, LateUpdate, and FixedUpdate phases in Unity.

Architecture

spinner

Data Flow

spinner

Key Concepts

Concept
Description

ParagonSynchronizationContext

Custom SynchronizationContext that replaces Unity's default. Routes async/await continuations to specific frame phases via WorkRunner queues.

CancellableTask

Wrapper for Func<Task> that provides cooperative cancellation via AsyncLocal<CancellableTask> and Yield event hooks. Used by the Action System for cancellable actions.

WorkRequest

Lightweight value type wrapping a callback and optional ManualResetEvent. Queued by runners and executed during the appropriate frame phase.

WorkExecutionTime

Enum selecting which frame phase a continuation should execute in: Update, LateUpdate, FixedUpdate, or EditorUpdate.

SynchronizationLoops

Marker structs registered with Unity's PlayerLoopSystem to trigger WorkRunner.Run() during specific frame phases.

Yield

Static API consumed by game code — provides WaitForUpdate(), WaitForSeconds(), etc. Posts continuations back to ParagonSynchronizationContext.

How It Works

  1. InitializationParagonSynchronizationContext installs itself as the active SynchronizationContext via [RuntimeInitializeOnLoadMethod] and injects custom loops into Unity's PlayerLoopSystem.

  2. Posting work — When code awaits a Yield method, the continuation is posted to the context's Post() method, which routes it to the appropriate WorkRunner based on WorkExecutionTime.

  3. Executing work — Each frame, Unity's player loop triggers the injected update delegates, which call WorkRunner.Run() to drain the queue and execute all pending continuations.

  4. CancellationCancellableTask sets itself as the AsyncLocal active task. When Cancel() is called, the next Yield call checks for cancellation via OnBeforeYield/OnAfterYield hooks and throws OperationCanceledException.

Quick Start

Awaiting Frame Phases

Running a Cancellable Task

Classes

Class
Description

Cooperative cancellation wrapper for async tasks

Custom SynchronizationContext routing continuations to frame phases

Marker structs for PlayerLoopSystem injection

Enum selecting the target frame phase for continuations

Value type wrapping a callback and optional wait handle

See Also

  • Action System — primary consumer of CancellableTask for cancellable actions

Last updated