UniTask
- 基于struct的
UniTask <T>和自定义异步分组以实现零分配 - 使所有的统一异步和共同点等待
- 基于playerloop的任务(
UniTask .Yield,UniTask .Delay,UniTask .DelayFrame等),可以更换所有Coroutine操作 - Monobehaviour消息事件和UGUI事件(等待/异步)可以实现
- 完全在Unity的PlayerLoop上运行,因此不使用线程并在WebGL,Wasm等上运行。
- 异步LINQ,带有通道和异步性Property
- 任务跟踪器窗口以防止内存泄漏
- 高度兼容的行为与任务/valueTask/ivaluetasksource
有关技术详细信息,请参阅博客文章: UniTask V2 – 零分配异步/等待Unity,以及异步LINQ
有关高级提示,请参见博客文章:通过异步装饰器图案扩展Unitywebrequest – UniTask的高级技术
目录
- 入门
- UniTask和异步的基础知识
- 取消和例外处理
- 超时处理
- 进步
- PlayerLoop
- 异步void vs async UniTask void
- UniTask跟踪器
- 外部资产
- 异步和异步LINQ
- 等待事件
- 渠道
- VS等待
- 用于单元测试
- ThreadPool限制
- ienumerator.to UniTask限制
- 对于Unityeditor
- 与标准任务API进行比较
- 合并配置
- 分配分配器
- UniTask同步context
- API参考
- UPM软件包
- 通过git URL安装
- .NET核心
- 执照
入门
通过UPM软件包安装git参考或资产软件包( UniTask .*.*.*.unitypackage ),可在UniTask /发布中提供。
UniTask ), it is unity specialized lightweight alternative of Task<T>
// zero allocation and fast excution for zero overhead async/await integrate with Unity
async UniTask <string> DemoAsync()
{
// You can await Unity\’s AsyncObject
var asset = await Resources.LoadAsync<TextAsset>(\”foo\”);
var txt = (await UnityWebRequest.Get(\”https://…\”).SendWebRequest()).downloadHandler.text;
await SceneManager.LoadSceneAsync(\”scene2\”);
// .WithCancellation enables Cancel, GetCancellationTokenOnDestroy synchornizes with lifetime of GameObject
// after Unity 2022.2, you can use `destroyCancellationToken` in MonoBehaviour
var asset2 = await Resources.LoadAsync<TextAsset>(\”bar\”).WithCancellation(this.GetCancellationTokenOnDestroy());
// .To UniTask accepts progress callback(and all options), Progress.Create is a lightweight alternative of IProgress<T>
var asset3 = await Resources.LoadAsync<TextAsset>(\”baz\”).To UniTask (Progress.Create<float>(x => Debug.Log(x)));
// await frame-based operation like a coroutine
await UniTask .DelayFrame(100);
// replacement of yield return new WaitForSeconds/WaitForSecondsRealtime
await UniTask .Delay(TimeSpan.FromSeconds(10), ignoreTimeScale: false);
// yield any playerloop timing(PreUpdate, Update, LateUpdate, etc…)
await UniTask .Yield(PlayerLoopTiming.PreLateUpdate);
// replacement of yield return null
await UniTask .Yield();
await UniTask .NextFrame();
// replacement of WaitForEndOfFrame
#if UNITY_2023_1_OR_NEWER
await UniTask .WaitForEndOfFrame();
#else
// requires MonoBehaviour(CoroutineRunner))
await UniTask .WaitForEndOfFrame(this); // this is MonoBehaviour
#endif
// replacement of yield return new WaitForFixedUpdate(same as UniTask .Yield(PlayerLoopTiming.FixedUpdate))
await UniTask .WaitForFixedUpdate();
// replacement of yield return WaitUntil
await UniTask .WaitUntil(() => isActive == false);
// special helper of WaitUntil
await UniTask .WaitUntilValueChanged(this, x => x.isActive);
// You can await IEnumerator coroutines
await FooCoroutineEnumerator();
// You can await a standard task
await Task.Run(() => 100);
// Multithreading, run on ThreadPool under this code
await UniTask .SwitchToThreadPool();
/* work on ThreadPool */
// return to MainThread(same as `ObserveOnMainThread` in UniRx)
await UniTask .SwitchToMainThread();
// get async webrequest
async UniTask <string> GetTextAsync(UnityWebRequest req)
{
var op = await req.SendWebRequest();
return op.downloadHandler.text;
}
var task1 = GetTextAsync(UnityWebRequest.Get(\”http://goog*l*e*.com\”));
var task2 = GetTextAsync(UnityWebRequest.Get(\”http://*bi*ng.co*m\”));
var task3 = GetTextAsync(UnityWebRequest.Get(\”http://yahoo***.com\”));
// concurrent async-wait and get results easily by tuple syntax
var (google, bing, yahoo) = await UniTask .WhenAll(task1, task2, task3);
// shorthand of WhenAll, tuple can await directly
var (google2, bing2, yahoo2) = await (task1, task2, task3);
// return async-value.(or you can use ` UniTask `(no result), ` UniTask Void`(fire and forget)).
return (asset as TextAsset)?.text ?? throw new InvalidOperationException(\”Asset not found\”);
}\”>
// extension awaiter/methods can be used by this namespace using Cysharp . Threading . Tasks ; // You can return type as struct UniTask <T>(or UniTask ), it is unity specialized lightweight alternative of Task<T> // zero allocation and fast excution for zero overhead async/await integrate with Unity async UniTask < string > DemoAsync ( ) { // You can await Unity\'s AsyncObject var asset = await Resources . LoadAsync < TextAsset > ( \"foo\" ) ; var txt = ( await UnityWebRequest . Get ( \"https://...\" ) . SendWebRequest ( ) ) . downloadHandler . text ; await SceneManager . LoadSceneAsync ( \"scene2\" ) ; // .WithCancellation enables Cancel, GetCancellationTokenOnDestroy synchornizes with lifetime of GameObject // after Unity 2022.2, you can use `destroyCancellationToken` in MonoBehaviour var asset2 = await Resources . LoadAsync < TextAsset > ( \"bar\" ) . WithCancellation ( this . GetCancellationTokenOnDestroy ( ) ) ; // .To UniTask accepts progress callback(and all options), Progress.Create is a lightweight alternative of IProgress<T> var asset3 = await Resources . LoadAsync < TextAsset > ( \"baz\" ) . To UniTask ( Progress . Create < float > ( x => Debug . Log ( x ) ) ) ; // await frame-based operation like a coroutine await UniTask . DelayFrame ( 100 ) ; // replacement of yield return new WaitForSeconds/WaitForSecondsRealtime await UniTask . Delay ( TimeSpan . FromSeconds ( 10 ) , ignoreTimeScale : false ) ; // yield any playerloop timing(PreUpdate, Update, LateUpdate, etc...) await UniTask . Yield ( PlayerLoopTiming . PreLateUpdate ) ; // replacement of yield return null await UniTask . Yield ( ) ; await UniTask . NextFrame ( ) ; // replacement of WaitForEndOfFrame #if UNITY_2023_1_OR_NEWER await UniTask . WaitForEndOfFrame ( ) ; #else // requires MonoBehaviour(CoroutineRunner)) await UniTask . WaitForEndOfFrame ( this ) ; // this is MonoBehaviour #endif // replacement of yield return new WaitForFixedUpdate(same as UniTask .Yield(PlayerLoopTiming.FixedUpdate)) await UniTask . WaitForFixedUpdate ( ) ; // replacement of yield return WaitUntil await UniTask . WaitUntil ( ( ) => isActive == false ) ; // special helper of WaitUntil await UniTask . WaitUntilValueChanged ( this , x => x . isActive ) ; // You can await IEnumerator coroutines await FooCoroutineEnumerator ( ) ; // You can await a standard task await Task . Run ( ( ) => 100 ) ; // Multithreading, run on ThreadPool under this code await UniTask . SwitchToThreadPool ( ) ; /* work on ThreadPool */ // return to MainThread(same as `ObserveOnMainThread` in UniRx) await UniTask . SwitchToMainThread ( ) ; // get async webrequest async UniTask < string > GetTextAsync ( UnityWebRequest req ) { var op = await req . SendWebRequest ( ) ; return op . downloadHandler . text ; } var task1 = GetTextAsync ( UnityWebRequest . Get ( \"http://goog*l*e*.com\" ) ) ; var task2 = GetTextAsync ( UnityWebRequest . Get ( \"http://*bi*ng.co*m\" ) ) ; var task3 = GetTextAsync ( UnityWebRequest . Get ( \"http://yahoo***.com\" ) ) ; // concurrent async-wait and get results easily by tuple syntax var ( google , bing , yahoo ) = await UniTask . WhenAll ( task1 , task2 , task3 ) ; // shorthand of WhenAll, tuple can await directly var ( google2 , bing2 , yahoo2 ) = await ( task1 , task2 , task3 ) ; // return async-value.(or you can use ` UniTask `(no result), ` UniTask Void`(fire and forget)). return ( asset as TextAsset ) ? . text ?? throw new InvalidOperationException ( \"Asset not found\" ) ; }
UniTask和异步的基础知识
UniTask功能依赖于C#7.0(类似任务的自定义ASYNC方法构建器功能),因此所需的Unity版本是在Unity 2018.3之后,支持的官方最低版本是Unity 2018.4.13f1 。
为什么需要使用UniTask (自定义任务样本对象)?因为任务太重,与Unity线程(单线程)不匹配。 UniTask不使用线程和同步context/executionContext,因为Unity的发动机层自动调度了Unity的异步对象。它实现了更快和降低的分配,并且与统一完全集成在一起。
您可以等待AsyncOperation , ResourceRequest , AssetBundleRequest , AssetBundleCreateRequest , UnityWebRequestAsyncOperation , AsyncGPUReadbackRequest ,iEnumerator,ienumerator, IEnumerator和其他using Cysharp.Threading.Tasks; 。
UniTask提供了三种扩展方法的模式。
* await asyncOperation ;
* . WithCancellation ( CancellationToken ) ;
* . To UniTask ( IProgress , PlayerLoopTiming , CancellationToken ) ;
WithCancellation是一个简单的To UniTask的版本,均为返回UniTask 。有关取消的详细信息,请参见:取消和异常处理部分。
注意:等待直接从playerloop的本机时间返回,但与指定的playerlooptiming一起返回了cancellation和UniTask 。有关定时的详细信息,请参见:PlayerLoop部分。
注意:AssetBundLereQuest具有
asset和allAssets,默认值等待返回asset。如果要获得allAssets,则可以使用AwaitForAllAssets()方法。
UniTask的类型可以使用UniTask .WhenAll whenall, UniTask .WhenAny , UniTask .WhenEach它们就像Task.WhenAll / Task.WhenAny当时,返回类型更有用。它们返回值元组,因此您可以解构每个结果并传递多种类型。
UniTaskVoid LoadManyAsync()
{
// parallel load.
var (a, b, c) = await UniTask .WhenAll(
LoadAsSprite(\”foo\”),
LoadAsSprite(\”bar\”),
LoadAsSprite(\”baz\”));
}
async UniTask <Sprite> LoadAsSprite(string path)
{
var resource = await Resources.LoadAsync<Sprite>(path);
return (resource as Sprite);
}\”>
public async UniTask Void LoadManyAsync ( ) { // parallel load. var ( a , b , c ) = await UniTask . WhenAll ( LoadAsSprite ( \"foo\" ) , LoadAsSprite ( \"bar\" ) , LoadAsSprite ( \"baz\" ) ) ; } async UniTask < Sprite > LoadAsSprite ( string path ) { var resource = await Resources . LoadAsync < Sprite > ( path ) ; return ( resource as Sprite ) ; }
如果要将回调转换为UniTask ,则可以使用UniTask CompletionSource<T>这是TaskCompletionSource<T>轻量级版本。
UniTask CompletionSource()
{
var utcs = new UniTask CompletionSource<int>();
// when complete, call utcs.TrySetResult();
// when failed, call utcs.TrySetException();
// when cancel, call utcs.TrySetCanceled();
return utcs.Task; //return UniTask <int>
}\”>
public UniTask < int > WrapBy UniTask CompletionSource ( ) { var utcs = new UniTask CompletionSource < int > ( ) ; // when complete, call utcs.TrySetResult(); // when failed, call utcs.TrySetException(); // when cancel, call utcs.TrySetCanceled(); return utcs . Task ; //return UniTask <int> }
您可以转换任务 – > UniTask : As UniTask , UniTask > UniTask <AsyncUnit> : AsAsyncUnit UniTask , UniTask <T> – > UniTask : As UniTask 。 UniTask <T> – > UniTask的转换成本是免费的。
如果要将异步转换为coroutine,则可以使用.ToCoroutine() ,如果您只允许使用Coroutine系统,这很有用。
UniTask不能等待两次。这与.NET标准2.1中引入的Valuetask/ivaluetasksource相似。
不应在Valuetask实例上执行以下操作:
- 多次等待实例。
- 多次致电ASTAKS。
- 使用.result或.getawaiter()。getResult()操作尚未完成或多次使用它们。
- 使用这些技术中的多种消耗实例。
如果您执行上述任何操作,则结果是不确定的。
UniTask.DelayFrame(10);
await task;
await task; // NG, throws Exception\”>
var task = UniTask . DelayFrame ( 10 ) ; await task ; await task ; // NG, throws Exception
存储到类字段,您可以使用UniTask .Lazy支持多次调用。 .Preserve()允许多个呼叫(内部缓存结果)。当功能范围中有多个调用时,这很有用。
同样, UniTask CompletionSource可以等待多次,并等待许多呼叫者。
取消和例外处理
某些UniTask工厂方法具有CancellationToken cancellationToken = default参数。同样,某些统一的异步操作具有WithCancellation(CancellationToken)和To UniTask (..., CancellationToken cancellation = default)扩展方法的扩展方法。
您可以通过标准CancellationTokenSource将CancellationToken传递给参数。
var cts = new CancellationTokenSource ( ) ;
cancelButton . onClick . AddListener ( ( ) =>
{
cts . Cancel ( ) ;
} ) ;
await UnityWebRequest . Get ( \"http://google.***co.jp\" ) . SendWebRequest ( ) . WithCancellation ( cts . Token ) ;
await UniTask . DelayFrame ( 1000 , cancellationToken : cts . Token ) ;
可以通过CancellationTokenSource或Monobehaviour的扩展方法GetCancellationTokenOnDestroy创建取消token。
// this CancellationToken lifecycle is same as GameObject.
await UniTask . DelayFrame ( 1000 , cancellationToken : this . GetCancellationTokenOnDestroy ( ) ) ;
对于传播取消,所有异步方法都建议在最终参数中接受CancellationToken cancellationToken ,然后将CancellationToken从根到结束。
UniTask FooAsync(CancellationToken cancellationToken)
{
await BarAsync(cancellationToken);
}
async UniTask BarAsync(CancellationToken cancellationToken)
{
await UniTask .Delay(TimeSpan.FromSeconds(3), cancellationToken);
}\”>
await FooAsync ( this . GetCancellationTokenOnDestroy ( ) ) ; // --- async UniTask FooAsync ( CancellationToken cancellationToken ) { await BarAsync ( cancellationToken ) ; } async UniTask BarAsync ( CancellationToken cancellationToken ) { await UniTask . Delay ( TimeSpan . FromSeconds ( 3 ) , cancellationToken ) ; }
CancellationToken表示异步的生命周期。您可以持有自己的生命周期,而代替默认的comcellationTokenEndestroy。
public class MyBehaviour : MonoBehaviour { CancellationTokenSource disableCancellation = new CancellationTokenSource ( ) ; CancellationTokenSource destroyCancellation = new CancellationTokenSource ( ) ; private void OnEnable ( ) { if ( disableCancellation != null ) { disableCancellation . Dispose ( ) ; } disableCancellation = new CancellationTokenSource ( ) ; } private void OnDisable ( ) { disableCancellation . Cancel ( ) ; } private void OnDestroy ( ) { destroyCancellation . Cancel ( ) ; destroyCancellation . Dispose ( ) ; } }
在Unity 2022.2之后,Unity在monobehaviour.destroycancellationtoken和application.exitCancellationToken中添加了concellationToken。
当检测到取消时,所有方法都会抛出OperationCanceledException并在上游传播。如果不使用异步方法处理异常(不限于OperationCanceledException ),则最终将其传播到UniTask Scheduler.UnobservedTaskException 。未接收的例外的默认行为是将日志写为例外。可以使用UniTask Scheduler.UnobservedExceptionWriteLogType更改日志级别。unobservedExceptionWritElogType。如果要使用自定义行为,请将操作设置为UniTask Scheduler.UnobservedTaskException.
而且OperationCanceledException是一个特殊的例外,在UnobservedTaskException时,这被默默地忽略。
如果您想在异步UniTask方法中取消行为,请手动扔OperationCanceledException 。
UniTask<int> FooAsync()
{
await UniTask .Yield();
throw new OperationCanceledException();
}\”>
public async UniTask < int > FooAsync ( ) { await UniTask . Yield ( ) ; throw new OperationCanceledException ( ) ; }
如果您处理异常,但想忽略(传播到全局取消处理),请使用异常过滤器。
UniTask<int> BarAsync()
{
try
{
var x = await FooAsync();
return x * 2;
}
catch (Exception ex) when (!(ex is OperationCanceledException)) // when (ex is not OperationCanceledException) at C# 9.0
{
return -1;
}
}\”>
public async UniTask < int > BarAsync ( ) { try { var x = await FooAsync ( ) ; return x * 2 ; } catch ( Exception ex ) when ( ! ( ex is OperationCanceledException ) ) // when (ex is not OperationCanceledException) at C# 9.0 { return - 1 ; } }
投掷/捕获OperationCanceledException略微沉重,因此,如果性能是一个问题,请使用UniTask .SuppressCancellationThrow ,以避免使用操作CanceLedException Throw。它返回(bool IsCanceled, T Result)而不是投掷。
UniTask.DelayFrame(10, cancellationToken: cts.Token).SuppressCancellationThrow();
if (isCanceled)
{
// …
}\”>
var ( isCanceled , _ ) = await UniTask . DelayFrame ( 10 , cancellationToken : cts . Token ) . SuppressCancellationThrow ( ) ; if ( isCanceled ) { // ... }
注意:仅当您直接呼叫到最源方法时,只会抑制投掷。否则,返回值将被转换,但是整个管道不会抑制投掷。
某些使用Unity播放器循环的功能,例如UniTask .Yield和UniTask .Delay等,确定了播放器循环上的comcellationToken状态。这意味着它不会在CancellationToken射击后立即取消。
如果您想更改此行为,则立即取消将其设置为参数,将cancelImmediately为comment。
await UniTask . Yield ( cancellationToken , cancelImmediately : true ) ;
注意: cancelImmediately设置为True并检测立即取消的设置比默认行为更为昂贵。这是因为它使用CancellationToken.Register ;它比在播放器循环上检查取消token的重量更重。
超时处理
超时是取消的变体。您可以通过CancellationTokenSouce.CancelAfterSlim(TimeSpan)设置超时,然后将comcellationToken传递到异步方法。
var cts = new CancellationTokenSource ( ) ; cts . CancelAfterSlim ( TimeSpan . FromSeconds ( 5 ) ) ; // 5sec timeout. try { await UnityWebRequest . Get ( \"http://**fo*o\" ) . SendWebRequest ( ) . WithCancellation ( cts . Token ) ; } catch ( OperationCanceledException ex ) { if ( ex . CancellationToken == cts . Token ) { UnityEngine . Debug . Log ( \"Timeout\" ) ; } }
CancellationTokenSouce.CancelAfter是标准API。但是,在统一上,您不应该使用它,因为它取决于线程计时器。CancelAfterSlim是UniTask的扩展方法,它使用PlayerLoop。
如果要将超时与其他取消源一起使用,请使用CancellationTokenSource.CreateLinkedTokenSource 。
var cancelToken = new CancellationTokenSource ( ) ; cancelButton . onClick . AddListener ( ( ) => { cancelToken . Cancel ( ) ; // cancel from button click. } ) ; var timeoutToken = new CancellationTokenSource ( ) ; timeoutToken . CancelAfterSlim ( TimeSpan . FromSeconds ( 5 ) ) ; // 5sec timeout. try { // combine token var linkedTokenSource = CancellationTokenSource . CreateLinkedTokenSource ( cancelToken . Token , timeoutToken . Token ) ; await UnityWebRequest . Get ( \"http://**fo*o\" ) . SendWebRequest ( ) . WithCancellation ( linkedTokenSource . Token ) ; } catch ( OperationCanceledException ex ) { if ( timeoutToken . IsCancellationRequested ) { UnityEngine . Debug . Log ( \"Timeout.\" ) ; } else if ( cancelToken . IsCancellationRequested ) { UnityEngine . Debug . Log ( \"Cancel clicked.\" ) ; } }
优化以减少compellationTokenSource的分配,以进行按呼叫异步方法的超时,您可以使用UniTask的TimeoutController 。
UniTask FooAsync()
{
try
{
// you can pass timeoutController.Timeout(TimeSpan) to cancellationToken.
await UnityWebRequest.Get(\”http://**fo*o\”).SendWebRequest()
.WithCancellation(timeoutController.Timeout(TimeSpan.FromSeconds(5)));
timeoutController.Reset(); // call Reset(Stop timeout timer and ready for reuse) when succeed.
}
catch (OperationCanceledException ex)
{
if (timeoutController.IsTimeout())
{
UnityEngine.Debug.Log(\”timeout\”);
}
}
}\”>
TimeoutController timeoutController = new TimeoutController ( ) ; // setup to field for reuse. async UniTask FooAsync ( ) { try { // you can pass timeoutController.Timeout(TimeSpan) to cancellationToken. await UnityWebRequest . Get ( \"http://**fo*o\" ) . SendWebRequest ( ) . WithCancellation ( timeoutController . Timeout ( TimeSpan . FromSeconds ( 5 ) ) ) ; timeoutController . Reset ( ) ; // call Reset(Stop timeout timer and ready for reuse) when succeed. } catch ( OperationCanceledException ex ) { if ( timeoutController . IsTimeout ( ) ) { UnityEngine . Debug . Log ( \"timeout\" ) ; } } }
如果要将超时与其他取消源一起使用,请使用new TimeoutController(CancellationToken) 。
TimeoutController timeoutController ; CancellationTokenSource clickCancelSource ; void Start ( ) { this . clickCancelSource = new CancellationTokenSource ( ) ; this . timeoutController = new TimeoutController ( clickCancelSource ) ; }
注意: UniTask具有.Timeout , .TimeoutWithoutException方法,但是,如果可能的话,请勿使用这些方法,请通过CancellationToken 。因为.Timeout .Timeout表示超时时忽略结果。如果将CancellationToken传递给该方法,它将从任务内部起作用,因此可以停止运行任务。
进步
统一的某些异步操作必须To UniTask (IProgress<float> progress = null, ...)扩展方法。
var progress = Progress . Create < float > ( x => Debug . Log ( x ) ) ;
var request = await UnityWebRequest . Get ( \"http://google.***co.jp\" )
. SendWebRequest ( )
. To UniTask ( progress : progress ) ;
您不应使用标准的new System.Progress<T> 。使用Cysharp.Threading.Tasks.Progress 。该进度工厂有两种方法, Create和CreateOnlyValueChanged 。仅当进度值更改时, CreateOnlyValue
