Migrating from UniTask to OnityTask
Use this page to replace UniTask 2.5.11 with OnityTask (Onity.Unity.Async) by renaming. PlayerLoop timings and timers, composition, cancellation helpers, completion sources, thread switches, Unity operations, coroutines, lifecycle and message triggers, UnityEvent and uGUI events, async LINQ streams, channels and async reactive properties keep UniTask’s shapes under Onity names. The remaining gaps are listed on the comparison page; UniTask’s External integrations (Addressables, DOTween, TextMeshPro) are out of scope. After the renames, read the behavior differences and, when you come from Onity 0.5, the upgrade notes.
Like UniTask, async OnityTask methods do not flow AsyncLocal<T> values across awaits by default. Set OnityTask.FlowExecutionContext = true before any async Onity method starts to opt in; on Unity’s Mono class library that costs an execution-context allocation per suspension only once a thread has stored an AsyncLocal<T> value.
For a task-oriented introduction, read Async with OnityTask. For the measured Release Player results and the feature coverage, read OnityTask vs UniTask.
Pooled-task safety: cancelable frame waits, timed waits, delays, predicates,
AsyncOperation.AsOnityTask()and suspended async methods return single-consumer values, as in UniTask. Await each value once. If several consumers must share the operation, callPreserve()once before sharing its returnedOnityTask, or callAsTask()once and share the returnedTask. In Play, a frame wait without a cancelable token andYield()are stateless and may be awaited by any number of consumers.
On this page: Namespaces and assembly references, API mapping, Behavior differences, Upgrading from Onity 0.5, Scene loading, Unity operations and web requests, Reactive and messaging bridges, Fire and forget, Guidance.
Namespaces and assembly references
using Onity.Unity.Async; // OnityTask, timings, composition, streams, channels, UI events
using Onity.Unity.Async.Triggers; // lifecycle and MonoBehaviour message triggers
Unity assembly references are not transitive. An assembly definition that calls Onity.Unity.Async extension methods must reference Onity.Reactive as well as Onity.Unity, and Onity.Messaging when it uses the messaging bridges: several extension names (AsOnityAsyncEnumerable, BindTo, ToOnityTask, WaitAsync) have overloads whose receiver or parameters are Onity.Reactive types, and the compiler reports CS0012 when it has to examine one without the reference. Code that touches Unit (OnityAsyncEnumerable.EveryUpdate(), AsUnitTask(), IOnityObservable<Unit>) also needs Onity.Core, where Unit lives. Add Onity.Unity.UGUI and UnityEngine.UI for the uGUI extensions.
API mapping
UniTask names map one to one unless a row says otherwise. A replacement of UniTask with OnityTask, UniTaskAsyncEnumerable with OnityAsyncEnumerable and IUniTask... with IOnity... covers most code.
Core
| UniTask 2.5.11 | Onity |
|---|---|
UniTask, UniTask<T>, async UniTask | OnityTask, OnityTask<T>, async OnityTask |
UniTaskVoid, async UniTaskVoid | OnityTaskVoid, async OnityTaskVoid |
UniTask.CompletedTask, FromResult, FromException(<T>), FromCanceled(<T>) | same names on OnityTask |
UniTask.Create, Defer, Never, Lazy | same names on OnityTask; AsyncLazy(<T>) is OnityAsyncLazy(<T>), task.ToAsyncLazy() |
UniTask.Void, UniTask.Action, UniTask.UnityAction | OnityTask.Void, OnityTask.Action, OnityTask.UnityAction |
task.Status, UniTaskStatus, UniTaskStatusExtensions | task.Status, OnityTaskStatus (Pending, Succeeded, Faulted, Canceled), OnityTaskStatusExtensions |
task.Preserve() | task.Preserve() |
task.AsUniTask() on UniTask<T>, implicit UniTask<T> to UniTask | task.AsOnityTask(), implicit OnityTask<T> to OnityTask |
task.AsAsyncUnitUniTask(), AsyncUnit | task.AsUnitTask(), Onity.Core.Unit |
PlayerLoop timing
| UniTask 2.5.11 | Onity |
|---|---|
PlayerLoopTiming.Update | OnityPlayerLoopTiming.UpdateBegin |
PlayerLoopTiming.FixedUpdate | OnityPlayerLoopTiming.FixedUpdateBegin |
Any other PlayerLoopTiming.X | OnityPlayerLoopTiming.X |
UniTask.Yield(), Yield(timing) (YieldAwaitable) | OnityTask.Yield(), Yield(timing) (OnityYieldAwaitable) |
UniTask.Yield(ct, cancelImmediately), Yield(timing, ct, cancelImmediately) | same shapes |
UniTask.NextFrame(timing, ct, cancelImmediately) | same shapes |
UniTask.DelayFrame(count, timing, ct, cancelImmediately) | OnityTask.DelayFrame(...), or Onity’s DelayFrames(count, ct) |
UniTask.WaitForFixedUpdate(ct, cancelImmediately) | same; NextFixedFrame(ct) resumes earlier, right after the fixed scripts |
UniTask.WaitForEndOfFrame(this, ct, cancelImmediately) | same, or OnityTask.WaitForEndOfFrame(ct) |
UniTask.Post(action, timing) | OnityTask.Post(action, timing) |
PlayerLoopHelper.AddAction, AddContinuation, IPlayerLoopItem | OnityTaskPlayerLoop.AddAction, AddContinuation, IOnityPlayerLoopItem |
PlayerLoopHelper.Initialize(ref loop, injectTimings) | OnityTaskPlayerLoop.Initialize(params timings) or InitializeAll(); Onity patches the current loop and installs other timings on first use |
PlayerLoopHelper.IsInjectedUniTaskPlayerLoop(), DumpCurrentPlayerLoop() | OnityTaskPlayerLoop.IsInjected(timing), DumpCurrentPlayerLoop() |
PlayerLoopHelper.IsMainThread, MainThreadId, UnitySynchronizationContext | same names on OnityTaskPlayerLoop |
Timed waits, timers and timeouts
| UniTask 2.5.11 | Onity |
|---|---|
UniTask.Delay(int millisecondsDelay, ...) | OnityTask.Delay(TimeSpan.FromMilliseconds(ms), ...); no integer overload, because OnityTask.Delay(2) means two seconds |
UniTask.Delay(TimeSpan, bool ignoreTimeScale, timing, ct, cancelImmediately) | same shape |
UniTask.Delay(TimeSpan, DelayType, timing, ct, cancelImmediately), DelayType | Delay(TimeSpan, OnityDelayType, ...), OnityDelayType |
UniTask.WaitForSeconds(float or int, ...) | OnityTask.WaitForSeconds(...) |
UniTask.WaitUntil / WaitWhile (predicate or state, timing, ct, cancelImmediately) | same shapes |
UniTask.WaitUntilCanceled(ct, timing, completeImmediately) | same |
UniTask.WaitUntilValueChanged(target, monitor, timing, comparer, ct, cancelImmediately) | same |
PlayerLoopTimer.Create, StartNew, Restart, Stop, Dispose | OnityPlayerLoopTimer |
cts.CancelAfterSlim(TimeSpan or int, DelayType, timing) | same with OnityDelayType |
TimeoutController | OnityTimeoutController |
task.Timeout(TimeSpan, DelayType, timing, cts), TimeoutWithoutException(...) | same with OnityDelayType; Onity also has Timeout(float seconds, bool useUnscaledTime = true) |
Threading
| UniTask 2.5.11 | Onity |
|---|---|
UniTask.SwitchToMainThread(ct), SwitchToMainThread(timing, ct) | same |
await using (UniTask.ReturnToMainThread()) | await using (OnityTask.ReturnToMainThread()) |
UniTask.SwitchToThreadPool() | OnityTask.SwitchToThreadPool(ct); WebGL Players reject it |
UniTask.SwitchToTaskPool(), SwitchToSynchronizationContext, ReturnToSynchronizationContext, ReturnToCurrentSynchronizationContext | same |
UniTask.RunOnThreadPool(action, configureAwait, ct) and its 8 shapes | OnityTask.RunOnThreadPool(action, returnToMainThread, ct) and the same shapes; returnToMainThread: true (the default) resumes on Unity’s main thread |
Composition
| UniTask 2.5.11 | Onity |
|---|---|
UniTask.WhenAll(params or IEnumerable) | same |
var (a, b) = await UniTask.WhenAll(t1, t2) (2 to 15 typed arguments) | same; same-type arguments also return a tuple |
UniTask.WhenAny(t1, t2, ...) with mixed types | same: (int winArgumentIndex, T1 result1, ...) |
UniTask.WhenAny(UniTask<T> left, UniTask right) | same: (bool hasResultLeft, T result) |
UniTask.WhenAny(params UniTask<T>[]) | same: (int winnerIndex, T result) |
UniTask.WhenEach(...), WhenEachResult<T> | OnityTask.WhenEach(...), OnityWhenEachResult<T> |
await (t1, t2), await tasks | same |
enumerable.Select(x => SomeAsync(x)) | same (OnityEnumerableAsyncExtensions) |
Cancellation
| UniTask 2.5.11 | Onity |
|---|---|
this.GetCancellationTokenOnDestroy() (MonoBehaviour, GameObject, Component) | same |
cts.RegisterRaiseCancelOnDestroy(component or gameObject) | same |
task.AttachExternalCancellation(ct), task.SuppressCancellationThrow() | same |
task.ToCancellationToken(), ToCancellationToken(linkToken) | same |
ct.ToUniTask() | ct.ToOnityTask() |
ct.WaitUntilCanceled(), disposable.AddTo(ct), ct.RegisterWithoutCaptureExecutionContext(...), ex.IsOperationCanceledException() | same |
CancellationTokenEqualityComparer.Default | OnityCancellationTokenEqualityComparer.Default |
Completion sources
| UniTask 2.5.11 | Onity |
|---|---|
UniTaskCompletionSource, UniTaskCompletionSource<T> | OnityTaskCompletionSource, OnityTaskCompletionSource<T> |
AutoResetUniTaskCompletionSource(<T>) (Create, CreateFromCanceled, CreateFromException, CreateCompleted, CreateFromResult, TrySet*) | OnityAutoResetTaskCompletionSource(<T>); the out tokens are int |
Unity operations
| UniTask 2.5.11 | Onity |
|---|---|
asyncOperation.ToUniTask(progress, timing, ct, cancelImmediately), await asyncOperation | asyncOperation.AsOnityTask(onProgress or IProgress<float>, ct); polled at Update, no timing or cancelImmediately overloads |
await resourceRequest, await assetBundleRequest (asset) | request.AsAssetOnityTask() / AsAssetOnityTask<T>() |
assetBundleRequest.AwaitForAllAssets() | same |
await assetBundleCreateRequest | request.AsAssetBundleOnityTask() |
await request.SendWebRequest() | operation.AsWebRequestOnityTask(), or OnityTask.Send(request, onProgress, ct) |
UnityWebRequestException | OnityUnityWebRequestException |
await asyncGpuReadbackRequest | request.AsOnityTask(ct) |
await instantiateOperation | operation.AsInstancesOnityTask() (Unity 2022.3.20+ or Unity 6) |
awaitable.AsUniTask() | awaitable.AsOnityTask() (Unity 2023.1 or newer) |
await jobHandle, jobHandle.WaitAsync(timing, ct), jobHandle.ToUniTask(timing) | await handle, handle.WaitAsync(timing, ct), handle.AsOnityTask() |
Progress.Create<T>, Progress.CreateOnlyValueChanged<T> | OnityProgress.Create<T>, OnityProgress.CreateOnlyValueChanged<T> |
await SceneManager.LoadSceneAsync("Game").ToUniTask(...) | await OnityTask.LoadScene("Game", onProgress, ct) (see Scene loading) |
Coroutines
| UniTask 2.5.11 | Onity |
|---|---|
UniTask.ToCoroutine(factory), task.ToCoroutine(resultHandler, exceptionHandler) | same |
await enumerator, enumerator.WithCancellation(ct) | same |
enumerator.ToUniTask(timing, ct), enumerator.ToUniTask(monoBehaviour) | enumerator.ToOnityTask(timing, ct), enumerator.ToOnityTask(monoBehaviour) |
this.StartAsyncCoroutine(token => ...) | same |
Triggers and UI events
| UniTask 2.5.11 | Onity |
|---|---|
using Cysharp.Threading.Tasks.Triggers; | using Onity.Unity.Async.Triggers; |
this.GetAsyncXTrigger(), XAsync(ct), GetXAsyncHandler(ct), IAsyncXHandler | same pattern: GetAsync<Name>Trigger(), <Message>Async(ct), Get<Message>AsyncHandler(ct), IOnityAsync<Message>Handler |
AwakeAsync, StartAsync, OnDestroyAsync, GetAsyncDestroyTrigger | same |
AsyncTriggerBase<T>, AsyncTriggerHandler<T>, TriggerEvent<T>, ITriggerHandler<T> | OnityAsyncTriggerBase<T>, OnityAsyncTriggerHandler<T>, OnityTriggerEvent<T>, IOnityTriggerHandler<T> |
unityEvent.OnInvokeAsync(ct), OnInvokeAsAsyncEnumerable(ct), GetAsyncEventHandler(ct) | same |
uGUI button.OnClickAsync(ct), slider.OnValueChangedAsync(ct), inputField.OnEndEditAsync(ct), EventSystems triggers | same, in the optional Onity.Unity.UGUI assembly |
stream.BindTo(text), BindTo(selectable), BindTo(target, action[, ct]) | same |
TextMeshPro BindTo(TMP_Text) | not provided; use stream.BindTo(label, (l, value) => l.text = value.ToString(), ct) |
UI Toolkit has no UniTask counterpart; Onity adds button.OnClickAsync(ct), element.OnEventAsync<TEvent>(ct), field.OnValueChangedAsync(ct), their stream forms and BindTo(TextElement).
Async streams
| UniTask 2.5.11 | Onity |
|---|---|
IUniTaskAsyncEnumerable<T>, IUniTaskAsyncEnumerator<T> | IOnityAsyncEnumerable<T>, IOnityAsyncEnumerator<T> |
UniTaskAsyncEnumerable.Create, Empty, Return, Range, Repeat, Never, Throw, Merge | OnityAsyncEnumerable.X |
UniTaskAsyncEnumerable.EveryUpdate, Timer, Interval, TimerFrame, IntervalFrame, EveryValueChanged | OnityAsyncEnumerable.X |
IAsyncWriter<T> | IOnityAsyncWriter<T> |
LINQ operators (Select, Where, Buffer, CombineLatest, GroupBy, SumAsync, ToListAsync, …) | same names (OnityAsyncEnumerableLinq) |
Subscribe, SubscribeAwait | same |
Publish(), Queue(), IConnectableUniTaskAsyncEnumerable<T> | same, IOnityConnectableAsyncEnumerable<T> |
IUniTaskOrderedAsyncEnumerable<T> | IOnityOrderedAsyncEnumerable<T> |
ToLookupAsync returning ILookup / IGrouping | IOnityLookup / IOnityGrouping |
enumerable.ToUniTaskAsyncEnumerable() (IEnumerable<T>, Task<T>, UniTask<T>) | ToOnityAsyncEnumerable() |
observable.ToUniTaskAsyncEnumerable() (IObservable<T>) | observable.AsOnityAsyncEnumerable(capacity) on an IOnityObservable<T> |
stream.ToObservable() | stream.AsObservable() (an IOnityObservable<T>) |
Channels and async reactive properties
| UniTask 2.5.11 | Onity |
|---|---|
Channel.CreateSingleConsumerUnbounded<T>() | OnityChannel.CreateUnbounded<T>(); Onity adds CreateBounded<T>(capacity) with waiting WriteAsync |
reader.ReadAsync, TryRead, ReadAllAsync, WaitToReadAsync, Completion | same |
writer.TryWrite, TryComplete, Complete | same |
ChannelClosedException | OnityChannelClosedException |
AsyncReactiveProperty<T>, ReadOnlyAsyncReactiveProperty<T> and their interfaces | OnityAsyncReactiveProperty<T>, OnityReadOnlyAsyncReactiveProperty<T>, IOnityAsyncReactiveProperty<T>, IOnityReadOnlyAsyncReactiveProperty<T> |
stream.ToReadOnlyAsyncReactiveProperty(ct) | same |
Scheduler, diagnostics and interop
| UniTask 2.5.11 | Onity |
|---|---|
UniTaskScheduler.UnobservedTaskException, PropagateOperationCanceledException, UnobservedExceptionWriteLogType, DispatchUnityMainThread | OnityTaskScheduler.X |
task.Forget(), Forget(handler, handleExceptionOnMainThread) | same |
TaskPool.SetMaxPoolSize(n) | OnityTask.SourcePoolCapacity = n, plus RunnerPoolCapacity for async-method runners |
TaskTracker and the UniTask Tracker window | OnityTaskTracker and Onity/Tools/Task Tracker |
task.ContinueWith(...), Unwrap() | same |
task.AsTask(), task.AsUniTask(useCurrentSynchronizationContext) on a Task | AsTask(), task.AsOnityTask(useCurrentSynchronizationContext) |
task.AsValueTask(), valueTask.AsUniTask() | AsValueTask(), valueTask.AsOnityTask() |
observable.ToUniTask(useFirstValue, ct) | observable.ToOnityTask(useFirstValue, ct) on an IOnityObservable<T>, or FirstOnityTask(ct) |
task.ToObservable() | same |
await asyncPublisher.PublishAsync(message, ct).AsTask() (MessagePipe) | await asyncPublisher.PublishOnityTask(message, ct) |
Behavior differences
Timing and frame waits:
- Onity’s default frame waits (
NextFrame,DelayFrames,Yield()) and the timed overloads at their default timing resume at Onity’sUpdatenode, after every MonoBehaviourUpdate. UniTask’s defaultPlayerLoopTiming.Updateruns before the scripts; useOnityPlayerLoopTiming.UpdateBeginwhere that order matters. The float-secondsDelay(seconds)and the token-onlyWaitUntilare ticked by a hidden runner component during theUpdatescripts. - In Play, frame waits without a cancelable token are stateless and may be awaited by any number of consumers; UniTask’s are single-consumer. Waits with a token are single-consumer in both.
WaitForFixedUpdate()resumes atLastFixedUpdate, after the physics step, like UniTask and Unity’s coroutine instruction; Onity’sNextFixedFrame()resumes earlier, right after the fixed scripts.DelayFrame(0)waits for the next drain, as in UniTask; Onity’sDelayFrames(0)completes immediately.- Onity’s timings other than
Update,FixedUpdateandLateUpdateinstall on first use; there is noInjectPlayerLoopTimingsmask to configure. - Onity’s pools keep at most 128 runners per async method and 256 sources per source type by default; UniTask keeps everything. Raise
OnityTask.RunnerPoolCapacity/SourcePoolCapacityfor methods that run in large bursts (see Bursts and pool retention).
Streams and operators:
CombineLatestyields one result per arriving item and queues results that arrive between moves, where UniTask overwrites the pending one; a source that ends without an item ends the stream.Publish()buffers every item for each registered enumerator, where UniTask drops items while an enumerator is not waiting, and disposing the connection ends the enumerators normally, where UniTask reports anOperationCanceledException.JoinandGroupJoinnever match anullkey (System.Linq semantics).ToLookupAsyncreturnsIOnityLookup, notSystem.Linq.ILookup.- Numeric aggregates follow System.Linq where UniTask’s template differs: a nullable
Sumstarts at zero,Averageof an empty non-nullable stream faults withInvalidOperationException, andAverageaccumulatesint/longinlongandfloatindouble. - Index-only
SelectAwait(Func<T, int, OnityTask<R>>)andWhereAwait(Func<T, int, OnityTask<bool>>)are not provided; useSelectAwaitWithCancellation((item, index, token) => ...). Onity’sSelectAwait,WhereAwaitandForEachAsyncalso accept a token-taking delegate, andForEachAsync(Func<T, OnityTask>)exists so that anasynclambda never becomes anasync voidaction. SkipUntilCanceled: the enumeration token cancels a pending move, where UniTask opens the stream.observable.AsOnityAsyncEnumerable(capacity)needs an explicit capacity, faults on overflow after draining the accepted values, and reports cancellation as a faultedOperationCanceledException.
Triggers, events and adapters:
- A one-shot
UnityEventhandler unregisters at its first invocation (UniTask waits until the result is read), and disposing a handler cancels its pending wait. - Trigger waits with a pre-canceled token, or started after the handler was disposed, canceled or destroyed, complete as canceled, where UniTask leaves them pending or throws.
AwakeAsyncon an inactive object completes when it is activated (UniTask only cancels it on destroy). cts.RegisterRaiseCancelOnDestroy(...)ignores a source that was disposed before the object is destroyed.- The Unity operation adapters report progress
1on their already-completed fast path, andAsyncGPUReadbackRequest.AsOnityTask()also checkshasErrorthere. OnityAsyncReactiveProperty<T>rejects a set from a continuation of its own publication before storing the value (UniTask stores it, then throws).
Context and faults:
- Flow is off by default, as in UniTask;
OnityTask.FlowExecutionContext = trueis an opt-in. While flow is on, code before an async method’s first await runs without a copy-on-write scope, so anAsyncLocal<T>write there can leak to the caller (UniTask behaves the same when it flows). Forget()without a handler,async OnityTaskVoidfaults and unobserved completion-source faults go toOnityTaskScheduler, which dropsOperationCanceledExceptionby default, as in UniTask.
Upgrading from Onity 0.5
These 0.6.0 changes can break code written against Onity 0.5:
OnityTask.FlowExecutionContextdefaults tofalse. Code that relies onAsyncLocal<T>values (logging scopes, correlation IDs, ambient services) surviving an await inside an async Onity method must set it totrueonce at startup, before any async Onity method runs.OnityTask.WhenAll(a, b, ...)with 2 to 15 separate typed arguments of the same type now returns a tuple instead ofT[]. Pass an array forT[]:OnityTask.WhenAll(new[] { a, b }).OnityTask.WhenAny<T>(OnityTask<T>, OnityTask<T>)returning(int winnerIndex, T result)was removed. Two typed arguments bind to the mixed overload, which returns(int winArgumentIndex, T result1, T result2); pass an array for(winnerIndex, result).OnityTask.Yield()andYield(timing)returnOnityYieldAwaitable. It converts implicitly toOnityTask, but extension methods onOnityTaskneed.ToOnityTask()first (OnityTask.Yield().ToOnityTask().Timeout(1f)).Yield(default)is ambiguous; pass a named argument (Yield(cancellationToken: ct)).- A
nullliteral passed toSelect,Where,SelectAwait,WhereAwaitorForEachAsyncon an async stream is ambiguous; cast it to the delegate type. The same applies toAsOnityTask(null, ct)on anAsyncOperationand tonew OnityChannelClosedException(null). - In Play, the default frame waits run on Onity’s PlayerLoop nodes, after the
Updatescripts, and are no longer canceled by destroying the legacy runner. Forget()without a handler no longer logsOperationCanceledException; setOnityTaskScheduler.PropagateOperationCanceledException = trueto report it.Releaseinto a pool thatBindPooledFactory(prefab, ...)created throwsObjectDisposedExceptionafter the pool’s scope ended, because the scope now disposes that pool.- Assemblies that call
Onity.Unity.Asyncextensions need anOnity.Reactivereference (see Namespaces and assembly references).
Scene loading
OnityTask.LoadScene, LoadSceneAdditive, UnloadScene and ActivateScene replace SceneManager.LoadSceneAsync(...).ToUniTask(...) (fragment):
await OnityTask.LoadScene("Gameplay", progress => ShowProgress(progress), ct);
await OnityTask.LoadSceneAdditive("GameplayUI", ct);
For delayed activation, LoadSceneAsync("Gameplay", activateOnLoad: false, cancellationToken: ct) returns the AsyncOperation. After Unity starts the load, the token cannot cancel it, so always activate the returned operation with OnityTask.ActivateScene(operation, CancellationToken.None), including when a later wait for input or a fade is canceled. See Scene loading in the guide.
Unity operations and web requests
Resources.LoadAsync<TextAsset>(path).AsAssetOnityTask<TextAsset>(cancellationToken: ct) returns the asset; the Unity operations table lists every adapter. OnityTask.Send awaits a UnityWebRequest that you own and dispose, and GetJson<TResponse> / PostJson<TRequest, TResponse> wrap JsonUtility DTOs (fragment):
using UnityWebRequest request = UnityWebRequest.Get(url);
UnityWebRequest completed = await OnityTask.Send(request, ct);
string json = completed.downloadHandler.text;
SaveResponse response = await OnityTask.PostJson<SaveRequest, SaveResponse>(url, saveRequest, ct);
A failed request throws OnityUnityWebRequestException (UniTask’s UnityWebRequestException). Use your own serializer around OnityTask.Send when a payload needs features JsonUtility does not support. See Web requests.
Reactive and messaging bridges
UniTask has no reactive property or message bus to await. Onity’s ReactiveProperty<T> (Onity.Reactive) has native waits, any IOnityObservable<T> converts to a task, and the async message channels (Onity.Messaging) publish and subscribe with OnityTask (fragment; the subscription field is disposed with its owner):
int wave = await m_wave.WaitUntilAsync(value => value >= 3, ct); // ReactiveProperty<int>; completes at once when already true
int report = await m_remainingEnemies.FirstOnityTask(ct); // Subject<int>, or any IOnityObservable<T>
await m_savedPublisher.PublishOnityTask(new InventorySaved(1), ct); // IAsyncPublisher<InventorySaved>
m_subscription = m_savedSubscriber.SubscribeOnityTask( // IAsyncSubscriber<InventorySaved>
async (message, token) => await ShowSavedAsync(message.Slot, token));
subscriber.ReceiveAsync(ct), ReceiveAllAsync(capacity, overflow) and SubscribeQueued(handler, capacity, lifetimeToken) add one-shot receives, buffered streams and queues with backpressure. See Reactive and messaging bridges, Reactive and Events and Messaging.
Fire and forget
OnityTask.LoadScene("Gameplay").Forget(Debug.LogException);
Without a handler, faults go to OnityTaskScheduler, which logs them and drops OperationCanceledException by default. While OnityTaskTracker.IsEnabled is true (the default), forgotten tasks are visible in Onity/Tools/Task Tracker. See Fire and forget.
Guidance
- Use
OnityTaskfor Unity-facing gameplay async flows. - Use normal
Taskfor plain .NET service APIs when that is already the project contract. - Keep
ValueTaskin engine-free low-level messaging paths where it avoids allocation and the API is already shipped. - Tie async work to its owner:
GetCancellationTokenOnDestroy()for a component, the scope token (IOnityScopeLifetime,GetScopeCancellationToken()) for a service. - Do not add UniTask as a runtime dependency just for frame waits, scene loads, web requests, or reactive awaits.