ParagonSynchronizationContext
Custom SynchronizationContext that replaces Unity's default. Routes async/await continuations to specific frame phases (Update, LateUpdate, FixedUpdate) via dedicated WorkRunner queues, and provides editor-mode async support with a throttled editor update runner. Installs itself automatically at both editor load and runtime initialization.
Definition
Namespace: Paragon.Core.Async Assembly: Paragon.dll
internal sealed class ParagonSynchronizationContext : SynchronizationContextInherits: System.Threading.SynchronizationContext Visibility: internal — not directly accessible by game code. Use the Yield API instead.
Remarks
Unity's default UnitySynchronizationContext posts all continuations to a single queue processed during Update. ParagonSynchronizationContext improves on this by:
Phase-specific routing — Continuations can target Update, LateUpdate, or FixedUpdate via WorkExecutionTime.
Editor support — A separate
editorUpdateRunnerprocesses async work in edit mode (not just Play Mode), throttled to 50ms ticks viaEditorApplication.update.Build safety — Automatically restores Unity's default context during compilation and builds, then re-installs afterward. This prevents async continuations from interfering with the build pipeline.
Installation Lifecycle
Editor load
[InitializeOnLoadMethod]
Creates context, subscribes to editor events
Runtime start
[RuntimeInitializeOnLoadMethod]
Creates context (if not already), injects PlayerLoopSystem entries
Compilation start
CompilationPipeline.compilationStarted
Restores Unity default context
Compilation end
CompilationPipeline.compilationFinished
Re-installs Paragon context
Build start
BuildPlayerProcessor.PrepareForBuild
Restores Unity default context
Build end
IPostprocessBuildWithReport
Re-installs Paragon context
Play mode exit
PlayModeStateChange.EnteredEditMode
Clears all runners
Application quit
Application.quitting
Clears all runners
PlayerLoop Injection
The context injects three custom loops into Unity's PlayerLoopSystem:
SynchronizationContextUpdate
PreUpdate
Append
updateRunner
SynchronizationContextLateUpdate
PreLateUpdate
Append
lateUpdateRunner
SynchronizationContextFixedUpdate
FixedUpdate
Prepend
fixedUpdateRunner
Quick Lookup
Post work to Update phase
context.Post(callback, WorkExecutionTime.UPDATE)
Post work to LateUpdate
context.Post(callback, WorkExecutionTime.LATE_UPDATE)
Post work to FixedUpdate
context.Post(callback, WorkExecutionTime.FIXED_UPDATE)
Send synchronously (main thread)
context.Send(callback, state) — executes inline
Send synchronously (background thread)
context.Send(callback, state) — queues and blocks
Game code should not access ParagonSynchronizationContext directly — it is internal. Use the Yield static API for all async frame-waiting operations.
Methods
Send
Synchronous send. If called from the main thread, executes the callback immediately. If called from a background thread, enqueues a WorkRequest with a ManualResetEvent and blocks until execution completes on the main thread.
callback
SendOrPostCallback
Delegate to execute
state
object
State passed to the callback
Post (Standard)
Asynchronous post. Queues work to the updateRunner (in Play Mode) or editorUpdateRunner (in Edit Mode). This is the override called by the C# async/await machinery.
callback
SendOrPostCallback
Delegate to execute
state
object
State passed to the callback
Post (Phase-Specific)
Extended post that routes work to a specific frame phase based on WorkExecutionTime.
callback
Action
Delegate to execute
executionTime
WorkExecutionTime
Target frame phase
In Edit Mode (not playing, not quitting), all work is routed to editorUpdateRunner regardless of the specified executionTime. This ensures editor async code works without the player loop running.
OperationStarted / OperationCompleted
Thread-safe operation count tracking via Interlocked.Increment / Decrement.
Common Pitfalls
Async work during compilation The context restores Unity's default SynchronizationContext during compilation. Any pending async continuations will be lost. This is by design — async operations should not survive domain reloads.
Edit mode throttling The editorUpdateRunner is throttled to 50ms intervals (editor_update_tick_rate). Async code in Edit Mode will not execute every editor frame — it runs at approximately 20 Hz.
PlayerLoop injection timing The PlayerLoopSystem injection happens in [RuntimeInitializeOnLoadMethod(SubsystemRegistration)], which runs very early. Code that depends on the player loop entries existing should not run before this.
See Also
SynchronizationContext System — system overview
CancellableTask — cooperative cancellation built on this context
WorkRequest — the work unit queued by runners
WorkExecutionTime — frame phase selection enum
SynchronizationLoops — marker structs for PlayerLoop injection
Last updated