OnityTask vs UniTask

Use this page to see how OnityTask measures against UniTask 2.5.11 and which UniTask APIs it covers. Every comparison pins official UniTask 2.5.11 at commit 2e993ff18f28c931602a07292df0b0804eebef99, and the current verification uses Unity 2022.3.62f2 Mono and IL2CPP Release Players. Each result applies to its stated workload and timing boundary; the separate benchmark harness defines every boundary and how to reproduce the run. Earlier runs and the experiments behind them are in the measurement history.

Latest Player verification (2026-10-02)

Onity 0.6.0 was measured against UniTask 2.5.11 in non-development Release Players (Unity 2022.3.62f2, Windows x64) built from source 6cc2115. Three Player processes per backend ran each of three suites; the gate reads the median and the worst process of the Onity/UniTask time ratio, so a ratio below 1 means OnityTask took less time. Onity ran with its default FlowExecutionContext = false (UniTask has no context flow) and with pool retention raised to the cohort for the 1,024- and 4,096-operation bursts, because UniTask’s pools retain every released object. The full report lists every row, the per-process ratios, the configuration and the raw data.

IL2CPP, the gate backend: all 29 gated rows pass. No process measured Onity slower in any gated row.

Suite and rows Rows Median Onity/UniTask Worst process
primary: consume a completed task or a synchronously completed async method (N = 1) 4 0.515-0.808 0.906
primary: NextFrame scheduling and GetResult (128, 4,096) 4 0.085-0.125 0.126
primary: async method awaiting NextFrame, typed and untyped, scheduling and GetResult (128, 4,096) 8 0.307-0.415 0.418
builderlifecycle: complete async-method lifecycle with 1 or 4 suspensions, typed and untyped (128, 4,096) 8 0.490-0.605 0.874
throughput: 16-await NextFrame / Yield loops under the real PlayerLoop (1,024, 4,096) 5 0.280-0.353 0.377

Mono, reported and not gated. Onity’s median is lower in 25 of 29 rows:

Suite and rows Rows Median Onity/UniTask
primary: completed results (N = 1) 4 1.087-1.924 (Onity slower)
primary: NextFrame scheduling and GetResult 4 0.120-0.319
primary: async method awaiting NextFrame 8 0.564-0.814
builderlifecycle, 1 suspension 4 0.885-0.909
builderlifecycle, 4 suspensions 4 0.932-0.973 (one process 1.009)
throughput 5 0.361-0.721

Report-only results to read with the gate:

  • Opt-in context flow. With FlowExecutionContext = true, every suspension captures and restores the execution context, which UniTask never does. The complete lifecycle with four suspensions is then 1.51-1.60x slower than UniTask on IL2CPP and 2.65-2.72x on Mono (128 and 4,096 calls); with one suspension it stays faster on IL2CPP (0.82-0.84) and is 1.63-1.65x slower on Mono. The synchronous scheduling and consumption slices stay faster with flow on (IL2CPP 0.31-0.53, Mono 0.72-0.82).
  • Default retention in 4,096-call bursts. With the default caps (128 runners per async method, 256 sources per source type) a burst above the cap allocates, and the complete lifecycle of three of the four IL2CPP rows is 1.47-2.58x slower (typed 4-suspension: 0.95); on Mono all four are 1.63-2.50x slower. Raise OnityTask.RunnerPoolCapacity / SourcePoolCapacity for bursty methods; see Bursts and pool retention. The 4,096 primary slices stay faster at the default caps.
  • Single calls. The builder lifecycle with one call at a time (N = 1) is 0.86-0.90 on IL2CPP and 0.97-1.00 on Mono.

Noise. The quiet pre-gate refused the host because other Unity Editors used CPU, and the noise screen flagged all 22 processes. On each backend 21 of the 29 rows have their three per-process ratios within 5% of each other; the widest IL2CPP spread is typed int with 4 suspensions at N = 128 (0.603-0.874).

Correctness. The same Mono and IL2CPP Players passed all 42 Player smoke cases. The generated IL2CPP C++ shows that the async-method runner calls its stored state machine by address with no copy back and clears it with il2cpp_codegen_initobj when it returns to the pool, which is what makes the immediate runner return safe, and that OnityTask members carry no class-initialization checks.

What changed since the archived measurements: pooled sources are class-rooted with a single-word completion protocol, pools are array-slot stacks with a shared SourcePoolCapacity, async-method runners return to their pool when their task is consumed on every backend, default frame waits are stateless in Play, Yield() returns a reference-free awaitable, OnityTask has no static constructor, the hot task, runner, source and PlayerLoop types turn off IL2CPP null checks, and context flow is off by default.

These are timing results for the measured workloads on one Windows PC. They do not establish allocation results, other platforms (ARM64, consoles, mobile) or Development Player behavior.

Feature coverage

Onity 0.6.0 and later cover UniTask 2.5.11’s runtime API under Onity names, except for the remaining gaps below; From UniTask maps each UniTask member. Addressables, DOTween and TextMeshPro integrations (UniTask’s External folder) are out of scope.

Area OnityTask (0.6.0 or newer)
Core task types OnityTask, OnityTask<T>, OnityTaskVoid, Onity method builders, Status / OnityTaskStatus, implicit OnityTask<T> to OnityTask view, Preserve(), CompletedTask, FromResult, FromException, FromCanceled, Create, Defer, Never, Lazy / OnityAsyncLazy, Void / Action / UnityAction
PlayerLoop timing 19 OnityPlayerLoopTiming members (UniTask’s 16 plus three after-script positions), Yield / NextFrame / DelayFrame with cancelImmediately, WaitForFixedUpdate, WaitForEndOfFrame, Post, OnityTaskPlayerLoop items, continuations, Initialize, InitializeAll, IsInjected, DumpCurrentPlayerLoop
Timed waits and timers Delay(TimeSpan, ignoreTimeScale or OnityDelayType, timing), WaitForSeconds, WaitUntil / WaitWhile with timing or state, WaitUntilCanceled, WaitUntilValueChanged, OnityPlayerLoopTimer, timed CancelAfterSlim, OnityTimeoutController, timed Timeout / TimeoutWithoutException
Threading SwitchToMainThread (also with a timing), ReturnToMainThread, SwitchToThreadPool, SwitchToTaskPool, SwitchToSynchronizationContext, ReturnToSynchronizationContext, ReturnToCurrentSynchronizationContext, every RunOnThreadPool shape
Composition WhenAll / WhenAny over arrays, sequences, typed tuples (2-15) and mixed types (2-15), left/right WhenAny, WhenEach, await on arrays, sequences and tuples, IEnumerable<T>.Select to tasks
Cancellation destroy tokens, RegisterRaiseCancelOnDestroy, AttachExternalCancellation, SuppressCancellationThrow, ToCancellationToken, token ToOnityTask / WaitUntilCanceled, AddTo(CancellationToken), RegisterWithoutCaptureExecutionContext, IsOperationCanceledException, OnityCancellationTokenEqualityComparer
Completion sources OnityTaskCompletionSource(<T>), OnityAutoResetTaskCompletionSource(<T>)
Unity operations AsyncOperation (with Action<float> or IProgress<float>), ResourceRequest, AssetBundleRequest, AssetBundleCreateRequest, UnityWebRequestAsyncOperation, AsyncGPUReadbackRequest, AsyncInstantiateOperation, Awaitable, JobHandle (await, AsOnityTask, WaitAsync), OnityProgress, OnityUnityWebRequestException
Coroutines ToCoroutine, awaiting IEnumerator, ToOnityTask (PlayerLoop or MonoBehaviour), StartAsyncCoroutine
Triggers and UI events lifecycle triggers, 55 MonoBehaviour message triggers, public trigger base and waiter list, UnityEvent waits and streams, uGUI waits, streams, EventSystems triggers and BindTo (optional Onity.Unity.UGUI), UI Toolkit waits, streams and BindTo (beyond UniTask)
Async streams native IOnityAsyncEnumerable<T>, Create with IOnityAsyncWriter<T>, Empty, Return, Range, Repeat, Never, Throw, timing streams, conversions, the async LINQ operators and consumers, Subscribe / SubscribeAwait, Publish, Queue, generic BindTo, BCL and reactive adapters
Channels bounded (beyond UniTask) and unbounded channels, Completion, WaitToReadAsync, Complete, ReadAllAsync
Async reactive properties OnityAsyncReactiveProperty<T>, OnityReadOnlyAsyncReactiveProperty<T>, ToReadOnlyAsyncReactiveProperty
Diagnostics and policy OnityTaskScheduler, Forget(handler, handleExceptionOnMainThread), task tracker, RunnerPoolCapacity / SourcePoolCapacity
Task interop ContinueWith, Unwrap, ValueTask / Task bridges, AsUnitTask, observable bridges

Beyond UniTask, the same package connects async work to DI scopes (IOnityScopeLifetime, AddTo(scope), GetScopeCancellationToken, IOnityAsyncInitializable, OnityContext.WaitReadyAsync), reactive properties (WaitAsync, WaitUntilAsync, AsLatestAsyncEnumerable) and message channels (ReceiveAsync, ReceiveAllAsync, SubscribeQueued), plus the bundled scene, web JSON and opt-in AsyncLocal<T> flow helpers.

Remaining gaps

  • PlayerLoopTiming and cancelImmediately overloads of the Unity operation adapters (AsyncOperation, ResourceRequest, AssetBundleRequest, AsyncGPUReadbackRequest); they poll at Update and observe cancellation at the next Update.
  • A native shareable WhenAll output: the array WhenAll paths still return a Task-backed output when inputs are pending.
  • UniTaskSynchronizationContext (no OnityTaskSynchronizationContext).
  • Index-only SelectAwait(Func<T, int, OnityTask<R>>) and WhereAwait(Func<T, int, OnityTask<bool>>), which would be ambiguous with Onity’s token forms; the indexed forms exist with a token.
  • Delay(int milliseconds): Delay(2) means two seconds in Onity; use Delay(TimeSpan.FromMilliseconds(ms)).
  • Public custom task sources (IUniTaskSource, UniTaskCompletionSourceCore<T>), the IPromise interfaces, TaskPool.GetCacheSizeInfo and the obsolete UniTask.Run.
  • Task tracking of async OnityTaskVoid starts (only Forget is tracked natively).

Earlier measurements

The dated sections that this page carried until 0.6.0 moved verbatim to OnityTask measurement history on 2026-10-02: the 2026-09-30 dispatch investigation, the 2026-09-29 and 2026-09-27 Player verifications, the Editor/Mono timing and allocation runs, the completion-source, builder, task-sharing, WhenAll and WhenAny experiments, the thread-switch comparison, the primary-suite rerun, the 0.4.0 thread-pool, Jobs and array WhenAny additions, and the superseded 0.4.0 coverage table. They describe the state at their date: context flow was on by default until 0.6.0, and statements such as “superiority remains unmet” are superseded by the verification above.

Thread-switch comparison

The 2026-09-25 Editor/Mono thread-switch measurements and their rerun after the drain rewrite are in the history page under Thread-switch comparison.

Primary suite rerun

The 2026-09-25 primary-suite rerun and the pooled-runner verification at 4be50dc and f682b7c are in the history page under Primary suite rerun.

See also