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/SourcePoolCapacityfor bursty methods; see Bursts and pool retention. The 4,096primaryslices 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
PlayerLoopTimingandcancelImmediatelyoverloads of the Unity operation adapters (AsyncOperation,ResourceRequest,AssetBundleRequest,AsyncGPUReadbackRequest); they poll at Update and observe cancellation at the next Update.- A native shareable
WhenAlloutput: the arrayWhenAllpaths still return a Task-backed output when inputs are pending. UniTaskSynchronizationContext(noOnityTaskSynchronizationContext).- Index-only
SelectAwait(Func<T, int, OnityTask<R>>)andWhereAwait(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; useDelay(TimeSpan.FromMilliseconds(ms)).- Public custom task sources (
IUniTaskSource,UniTaskCompletionSourceCore<T>), theIPromiseinterfaces,TaskPool.GetCacheSizeInfoand the obsoleteUniTask.Run. - Task tracking of
async OnityTaskVoidstarts (onlyForgetis 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.