UniTask

C#源码 2025-08-16

UniTask

为统一提供有效的无异步分配/等待集成。

  • 基于struct的UniTask <T>和自定义异步分组以实现零分配
  • 使所有的统一异步和共同点等待
  • 基于playerloop的任务( UniTask .YieldUniTask .DelayUniTask .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的异步对象。它实现了更快和降低的分配,并且与统一完全集成在一起。

您可以等待AsyncOperationResourceRequestAssetBundleRequestAssetBundleCreateRequestUnityWebRequestAsyncOperationAsyncGPUReadbackRequest ,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具有assetallAssets ,默认值等待返回asset 。如果要获得allAssets ,则可以使用AwaitForAllAssets()方法。

UniTask的类型可以使用UniTask .WhenAll whenall, UniTask .WhenAnyUniTask .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 UniTaskUniTask > UniTask <AsyncUnit>AsAsyncUnit UniTaskUniTask <T> - > UniTaskAs UniTaskUniTask <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)扩展方法的扩展方法。

您可以通过标准CancellationTokenSourceCancellationToken传递给参数。

 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 .YieldUniTask .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 。该进度工厂有两种方法, CreateCreateOnlyValueChanged 。仅当进度值更改时, CreateOnlyValue

下载源码

通过命令行克隆项目:

git clone https://github.com/Cysharp/UniTask.git