为统一提供有效的无异步分配/等待集成。
UniTask<T>
和自定义异步分组以实现零分配UniTask.Yield
, UniTask.Delay
, UniTask.DelayFrame
等),可以更换所有Coroutine操作有关技术详细信息,请参阅博客文章:Unitask V2 - 零分配异步/等待Unity,以及异步LINQ
有关高级提示,请参见博客文章:通过异步装饰器图案扩展Unitywebrequest - Unitask的高级技术
通过UPM软件包安装git参考或资产软件包( UniTask.*.*.*.unitypackage
),可在Unitask/发布中提供。
// 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 ( ) ) ;
// .ToUniTask accepts progress callback(and all options), Progress.Create is a lightweight alternative of IProgress<T>
var asset3 = await Resources . LoadAsync < TextAsset > ( " baz " ) . ToUniTask ( 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://google.com " ) ) ;
var task2 = GetTextAsync ( UnityWebRequest . Get ( " http://bing.com " ) ) ;
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), `UniTaskVoid`(fire and forget)).
return ( asset as TextAsset ) ? . text ?? throw new InvalidOperationException ( " Asset not found " ) ;
}
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 ) ;
* . ToUniTask ( IProgress , PlayerLoopTiming , CancellationToken ) ;
WithCancellation
是ToUniTask
的简单版本,均为返回UniTask
。有关取消的详细信息,请参见:取消和异常处理部分。
注意:等待直接从playerloop的本机时间返回,但与指定的playerlooptiming一起返回了cancellation和tounitask。有关定时的详细信息,请参见:PlayerLoop部分。
注意:AssetBundLereQuest具有
asset
和allAssets
,默认值等待返回asset
。如果要获得allAssets
,则可以使用AwaitForAllAssets()
方法。
UniTask
的类型可以使用UniTask.WhenAll
, UniTask.WhenAny
, UniTask.WhenEach
等实用程序。它们就像Task.WhenAll
/ Task.WhenAny
当时,返回类型更有用。它们返回值元组,因此您可以解构每个结果并传递多种类型。
public async 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 ) ;
}
如果要将回调转换为unitask,则可以使用UniTaskCompletionSource<T>
这是TaskCompletionSource<T>
轻量级版本。
public UniTask < int > WrapByUniTaskCompletionSource ( )
{
var utcs = new UniTaskCompletionSource < int > ( ) ;
// when complete, call utcs.TrySetResult();
// when failed, call utcs.TrySetException();
// when cancel, call utcs.TrySetCanceled();
return utcs . Task ; //return UniTask<int>
}
您可以转换任务 - > unitask: AsUniTask
, UniTask
> UniTask<AsyncUnit>
: AsAsyncUnitUniTask
, UniTask<T>
- > UniTask
: AsUniTask
。 UniTask<T>
- > UniTask
的转换成本是免费的。
如果要将异步转换为coroutine,则可以使用.ToCoroutine()
,如果您只允许使用Coroutine系统,这很有用。
Unitask不能等待两次。这与.NET标准2.1中引入的Valuetask/ivaluetasksource相似。
不应在Valuetask实例上执行以下操作:
- 多次等待实例。
- 多次致电ASTAKS。
- 使用.result或.getawaiter()。getResult()操作尚未完成或多次使用它们。
- 使用这些技术中的多种消耗实例。
如果您执行上述任何操作,则结果是不确定的。
var task = UniTask . DelayFrame ( 10 ) ;
await task ;
await task ; // NG, throws Exception
存储到类字段,您可以使用UniTask.Lazy
支持多次调用。 .Preserve()
允许多个呼叫(内部缓存结果)。当功能范围中有多个调用时,这很有用。
同样, UniTaskCompletionSource
可以等待多次,并等待许多呼叫者。
某些Unitask工厂方法具有CancellationToken cancellationToken = default
参数。同样,某些统一的异步操作具有与WithCancellation(CancellationToken)
和ToUniTask(..., 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
从根到结束。
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
),则最终将其传播到UniTaskScheduler.UnobservedTaskException
。未接收的例外的默认行为是将日志写为例外。可以使用UniTaskScheduler.UnobservedExceptionWriteLogType
更改日志级别。如果要使用自定义行为,请将操作设置为UniTaskScheduler.UnobservedTaskException.
而且OperationCanceledException
是一个特殊的例外,在UnobservedTaskException
时,这被默默地忽略。
如果您想在异步一键方法中取消行为,请手动扔OperationCanceledException
。
public async UniTask < int > FooAsync ( )
{
await UniTask . Yield ( ) ;
throw new OperationCanceledException ( ) ;
}
如果您处理异常,但想忽略(传播到全局取消处理),请使用异常过滤器。
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
,以避免使用操作cancanceledexception throw。它返回(bool IsCanceled, T Result)
而不是投掷。
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://foo " ) . 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://foo " ) . SendWebRequest ( ) . WithCancellation ( linkedTokenSource . Token ) ;
}
catch ( OperationCanceledException ex )
{
if ( timeoutToken . IsCancellationRequested )
{
UnityEngine . Debug . Log ( " Timeout. " ) ;
}
else if ( cancelToken . IsCancellationRequested )
{
UnityEngine . Debug . Log ( " Cancel clicked. " ) ;
}
}
优化以减少concellationTokenSource的分配,以进行每个呼叫async方法的超时,您可以使用Unitask的TimeoutController
。
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://foo " ) . 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
传递给该方法,它将从任务内部起作用,因此可以停止运行任务。
统一的某些异步操作具有ToUniTask(IProgress<float> progress = null, ...)
扩展方法。
var progress = Progress . Create < float > ( x => Debug . Log ( x ) ) ;
var request = await UnityWebRequest . Get ( " http://google.co.jp " )
. SendWebRequest ( )
. ToUniTask ( progress : progress ) ;
您不应使用标准的new System.Progress<T>
。使用Cysharp.Threading.Tasks.Progress
。该进度工厂有两种方法,分别Create
和CreateOnlyValueChanged
。仅当进度值更改时, CreateOnlyValueChanged
呼叫。
将IProgress接口实现到呼叫者更好,因为没有LAMBDA分配。
public class Foo : MonoBehaviour , IProgress < float >
{
public void Report ( float value )
{
UnityEngine . Debug . Log ( value ) ;
}
public async UniTaskVoid WebRequest ( )
{
var request = await UnityWebRequest . Get ( " http://google.co.jp " )
. SendWebRequest ( )
. ToUniTask ( progress : this ) ; // pass this
}
}
unitask在自定义playerloop上运行。基于unitask的PlayerLoopTiming
方法(例如Delay
, DelayFrame
, asyncOperation.ToUniTask
。
public enum PlayerLoopTiming
{
Initialization = 0 ,
LastInitialization = 1 ,
EarlyUpdate = 2 ,
LastEarlyUpdate = 3 ,
FixedUpdate = 4 ,
LastFixedUpdate = 5 ,
PreUpdate = 6 ,
LastPreUpdate = 7 ,
Update = 8 ,
LastUpdate = 9 ,
PreLateUpdate = 10 ,
LastPreLateUpdate = 11 ,
PostLateUpdate = 12 ,
LastPostLateUpdate = 13
# if UNITY_2020_2_OR_NEWER
TimeUpdate = 14 ,
LastTimeUpdate = 15 ,
#endif
}
它指示何时运行,您可以检查playerlooplist.md到Unity的默认PlayerLoop和注入Unitask的自定义循环。
PlayerLoopTiming.Update
类似于在coroutine中的yield return null
,但是在更新之前称为(更新和UGUI事件(button.onclick等...),在ScriptRunBehaviourUpdate
上调用, ScriptRunDelayedDynamicFrameRate
在scriptrunbehaviourupdate上称为null)。 PlayerLoopTiming.FixedUpdate
类似于WaitForFixedUpdate
。
PlayerLoopTiming.LastPostLateUpdate
不等于Coroutine的yield return new WaitForEndOfFrame()
。 Coroutine的Waitforendofframe似乎在完成PlayerLoop完成后运行。一些需要Coroutine末端的方法(Texture2D.ReadPixels
,ScreenCapture.CaptureScreenshotAsTexture
,CommandBuffer
等)在替换异步/等待时无法正常工作。在这些情况下,将Monobehaviour(Coroutine Runnner)传递到UniTask.WaitForEndOfFrame
。例如,await UniTask.WaitForEndOfFrame(this);
是轻巧的分配免费替代yield return new WaitForEndOfFrame()
。注意:在Unity 2023.1或更新中,
await UniTask.WaitForEndOfFrame();
不再需要Monobehaviour。它使用UnityEngine.Awaitable.EndOfFrameAsync
。
yield return null
和UniTask.Yield
相似但不同。 yield return null
总是返回下一个帧,但UniTask.Yield
返回接下来。也就是说,请致电UniTask.Yield(PlayerLoopTiming.Update)
在PreUpdate
上,它返回同一帧。 UniTask.NextFrame()
保证返回下一个帧,您可以期望这与yield return null
完全相同。
unitask.yield(无取消用语)是一种特殊的类型,返回
YieldAwaitable
,并在faredrunner上运行。它是最轻巧,最快的。
AsyncOperation
从本机时间返回。例如,等待SceneManager.LoadSceneAsync
从EarlyUpdate.UpdatePreloading
返回,在被称为后,已加载的场景的Start
是从EarlyUpdate.ScriptRunDelayedStartupFrame
调用的。同样, await UnityWebRequest
从EarlyUpdate.ExecuteMainThreadJobs
返回。
在Unitask中,等待直接使用本机定时,而WithCancellation
和ToUniTask
使用指定的时机。这通常不是一个特定的问题,但是对于LoadSceneAsync
,它会在等待后导致不同的起始和延续顺序。因此,建议不要使用LoadSceneAsync.ToUniTask
。
注意:使用Unity 2023.1或更新时,请确保
using UnityEngine;
在使用新的UnityEngine.Awaitable
时,在文件的使用语句中,如SceneManager.LoadSceneAsync
。通过避免使用UnityEngine.AsyncOperation
版本,这可以防止编译错误。
在StackTrace中,您可以在PlayerLoop中检查其在哪里运行。
默认情况下,Unitask的PlayerLoop在[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
中进行了初始化。
在beforesceneload中调用方法的顺序是非确定性的,因此,如果要在其他beforesceneload方法中使用unitask,则应在此之前尝试初始化它。
// AfterAssembliesLoaded is called before BeforeSceneLoad
[ RuntimeInitializeOnLoadMethod ( RuntimeInitializeLoadType . AfterAssembliesLoaded ) ]
public static void InitUniTaskLoop ( )
{
var loop = PlayerLoop . GetCurrentPlayerLoop ( ) ;
Cysharp . Threading . Tasks . PlayerLoopHelper . Initialize ( ref loop ) ;
}
如果您导入Unity的Entities
软件包,则将自定义播放器循环重置为在BeforeSceneLoad
上默认并注入ECS的循环。当Unity在Unitask的初始化方法之后调用Unity调用ECS的入口方法时,Unitask将不再起作用。
为了解决此问题,您可以在ECS初始化后重新启动Unitask PlayerLoop。
// Get ECS Loop.
var playerLoop = ScriptBehaviourUpdateOrder . CurrentPlayerLoop ;
// Setup UniTask's PlayerLoop.
PlayerLoopHelper . Initialize ( ref playerLoop ) ;
您可以通过调用PlayerLoopHelper.IsInjectedUniTaskPlayerLoop()
来诊断Unitask的播放器循环是否准备就绪。以及PlayerLoopHelper.DumpCurrentPlayerLoop
log log s sonemole当前的playerLoops。
void Start ( )
{
UnityEngine . Debug . Log ( " UniTaskPlayerLoop ready? " + PlayerLoopHelper . IsInjectedUniTaskPlayerLoop ( ) ) ;
PlayerLoopHelper . DumpCurrentPlayerLoop ( ) ;
}
您可以通过删除未使用播放器浮动注入来稍微优化循环成本。您可以在初始化时调用PlayerLoopHelper.Initialize(InjectPlayerLoopTimings)
。
var loop = PlayerLoop . GetCurrentPlayerLoop ( ) ;
PlayerLoopHelper . Initialize ( ref loop , InjectPlayerLoopTimings . Minimum ) ; // minimum is Update | FixedUpdate | LastPostLateUpdate
InjectPlayerLoopTimings
具有三个预设, All
和Standard
(除了LastPostLateUpdate外没有最后一个预设), Minimum
( Update | FixedUpdate | LastPostLateUpdate
)。默认值是全部,您可以组合自定义注射时间,例如InjectPlayerLoopTimings.Update | InjectPlayerLoopTimings.FixedUpdate | InjectPlayerLoopTimings.PreLateUpdate
。
您可以通过Microsoft.codeanalysis.bannedapianalyzers使用未注入的PlayerLoopTiming
来犯错。例如,您可以将类似的BannedSymbols.txt
设置为InjectPlayerLoopTimings.Minimum
。
F:Cysharp.Threading.Tasks.PlayerLoopTiming.Initialization; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastInitialization; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.EarlyUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastEarlyUpdate; Isn't injected this PlayerLoop in this project.d
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastFixedUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.PreUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastPreUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.PreLateUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastPreLateUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.PostLateUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.TimeUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastTimeUpdate; Isn't injected this PlayerLoop in this project.
您可以将RS0030
严重性配置为错误。
async void
是标准C#任务系统,因此它不在unitask系统上运行。最好不要使用它。 async UniTaskVoid
是async UniTask
的轻量级版本,因为它没有等待的完成,并立即向UniTaskScheduler.UnobservedTaskException
报告错误。unobsevedtaskexception。如果您不需要等待(火和忘记),则使用UniTaskVoid
会更好。不幸的是要驳回警告,您需要打电话Forget()
。
public async UniTaskVoid FireAndForgetMethod ( )
{
// do anything...
await UniTask . Yield ( ) ;
}
public void Caller ( )
{
FireAndForgetMethod ( ) . Forget ( ) ;
}
此外,unitask具有Forget
方法,它类似于UniTaskVoid
,并且具有相同的效果。但是UniTaskVoid
如果您完全不使用await
。
public async UniTask DoAsync ( )
{
// do anything...
await UniTask . Yield ( ) ;
}
public void Caller ( )
{
DoAsync ( ) . Forget ( ) ;
}
要使用注册到事件的异步lambda,请不要使用async void
。取而代之的是,您可以使用UniTask.Action
或UniTask.UnityAction
,这两者都通过async UniTaskVoid
lambda创建一个代表。
Action actEvent ;
UnityAction unityEvent ; // especially used in uGUI
// Bad: async void
actEvent += async ( ) => { } ;
unityEvent += async ( ) => { } ;
// Ok: create Action delegate by lambda
actEvent += UniTask . Action ( async ( ) => { await UniTask . Yield ( ) ; } ) ;
unityEvent += UniTask . UnityAction ( async ( ) => { await UniTask . Yield ( ) ; } ) ;
UniTaskVoid
也可以在Monobehaviour的Start
方法中使用。
class Sample : MonoBehaviour
{
async UniTaskVoid Start ( )
{
// async init code.
}
}
可用于检查(泄漏)的unitask。您可以在Window -> UniTask Tracker
。
UnitaskTracker旨在仅用于调试使用,因为启用跟踪和捕获stackTraces非常有用,但性能影响很大。推荐的用法是启用跟踪和stackTraces以查找任务泄漏并在完成后将其禁用。
默认情况下,Unitask支持TextMeshPro( BindTo(TMP_Text)
和TMP_InputField
事件Extensions,例如标准UGUI InputField
),dotwee Tween
(等待)和Promersables( AsyncOperationHandle
和asyncoperationHandle和AsyncOperationHandle<T>
等待等待)。
在分离的ASMDEF中定义了UniTask.TextMeshPro
, UniTask.DOTween
, UniTask.Addressables
。
从软件包管理器导入其软件包时,TextMeshPro和PromereAbles支持将自动启用。但是,对于支持之间的支持,从资产之间导入并定义脚本定义符号UNITASK_DOTWEEN_SUPPORT
以启用它。
// sequential
await transform . DOMoveX ( 2 , 10 ) ;
await transform . DOMoveZ ( 5 , 20 ) ;
// parallel with cancellation
var ct = this . GetCancellationTokenOnDestroy ( ) ;
await UniTask . WhenAll (
transform . DOMoveX ( 10 , 3 ) . WithCancellation ( ct ) ,
transform . DOScale ( 10 , 3 ) . WithCancellation ( ct ) ) ;
支持支持的默认行为( await
, WithCancellation
, ToUniTask
)等待Tween被杀死。它在完整(true/fals)和杀死(true/false)上都起作用。但是,如果您想重复使用Tweens( SetAutoKill(false)
),则它无法正常工作。如果您想等待另一个时间安排,则在Tween, AwaitForComplete
, AwaitForPause
Pape, AwaitForPlay
, AwaitForRewind
AwaitForStepComplete
中存在以下扩展方法。
Unity 2020.2支持C#8.0,因此您可以使用await foreach
。这是异步时代的新更新表示法。
// Unity 2020.2, C# 8.0
await foreach ( var _ in UniTaskAsyncEnumerable . EveryUpdate ( ) . WithCancellation ( token ) )
{
Debug . Log ( " Update() " + Time . frameCount ) ;
}
在C#7.3环境中,您可以使用ForEachAsync
方法以几乎相同的方式工作。
// C# 7.3(Unity 2018.3~)
await UniTaskAsyncEnumerable . EveryUpdate ( ) . ForEachAsync ( _ =>
{
Debug . Log ( " Update() " + Time . frameCount ) ;
} , token ) ;
UniTask.WhenEach
与.NET 9的Task.WhenEach
相似。Wheneach可以消除等待多个任务的新方法。
await foreach ( var result in UniTask . WhenEach ( task1 , task2 , task3 ) )
{
// The result is of type WhenEachResult<T>.
// It contains either `T Result` or `Exception Exception`.
// You can check `IsCompletedSuccessfully` or `IsFaulted` to determine whether to access `.Result` or `.Exception`.
// If you want to throw an exception when `IsFaulted` and retrieve the result when successful, use `GetResult()`.
Debug . Log ( result . GetResult ( ) ) ;
}
unitaskAsyncenumerable会激发异步LINQ,类似于IObservable<T>
IEnumerable<T>
<t>中的linq或rx。所有标准的LINQ查询操作员都可以应用于异步流。例如,以下代码显示了如何将Where滤波器应用于单击每两个点击一次的按钮单与同步流。
await okButton . OnClickAsAsyncEnumerable ( ) . Where ( ( x , i ) => i % 2 == 0 ) . ForEachAsync ( _ =>
{
} ) ;
火与忘记样式(例如,事件处理),您也可以使用Subscribe
。
okButton . OnClickAsAsyncEnumerable ( ) . Where ( ( x , i ) => i % 2 == 0 ) . Subscribe ( _ =>
{
} ) ;
using Cysharp.Threading.Tasks.Linq;
, UniTaskAsyncEnumerable
在UniTask.Linq
asmdef中定义。
它更接近unirx(反应性扩展),但是unitaskAsyncenumerable是一种基于拉动的异步流,而RX是基于推动的异步流。请注意,尽管相似,但特征是不同的,细节与它们一起行为不同。
UniTaskAsyncEnumerable
是Enumerable
的入口点。除了标准查询运算符外,还有其他用于统一的发电机,例如EveryUpdate
, Timer
, TimerFrame
, Interval
, IntervalFrame
和EveryValueChanged
。 And also added additional UniTask original query operators like Append
, Prepend
, DistinctUntilChanged
, ToHashSet
, Buffer
, CombineLatest
, Merge
Do
, Never
, ForEachAsync
, Pairwise
, Publish
, Queue
, Return
, SkipUntil
, TakeUntil
, SkipUntilCanceled
, TakeUntilCanceled
, TakeLast
, Subscribe
.
以弹性为论点的方法还有三个额外的过载, ***Await
, ***AwaitWithCancellation
。
Select ( Func < T , TR > selector )
SelectAwait ( Func < T , UniTask < TR > > selector )
SelectAwaitWithCancellation ( Func < T , CancellationToken , UniTask < TR > > selector )
如果您想在func内使用async
方法,请使用***Await
或***AwaitWithCancellation
。
如何创建异步迭代器:C#8.0支持异步迭代器( async yield return
),但它仅允许IAsyncEnumerable<T>
,当然也需要C#8.0。 unitask支持UniTaskAsyncEnumerable.Create
方法来创建自定义异步迭代器。
// IAsyncEnumerable, C# 8.0 version of async iterator. ( do not use this style, IAsyncEnumerable is not controled in UniTask).
public async IAsyncEnumerable < int > MyEveryUpdate ( [ EnumeratorCancellation ] CancellationToken cancelationToken = default )
{
var frameCount = 0 ;
await UniTask . Yield ( ) ;
while ( ! token . IsCancellationRequested )
{
yield return frameCount ++ ;
await UniTask . Yield ( ) ;
}
}
// UniTaskAsyncEnumerable.Create and use `await writer.YieldAsync` instead of `yield return`.
public IUniTaskAsyncEnumerable < int > MyEveryUpdate ( )
{
// writer(IAsyncWriter<T>) has `YieldAsync(value)` method.
return UniTaskAsyncEnumerable . Create < int > ( async ( writer , token ) =>
{
var frameCount = 0 ;
await UniTask . Yield ( ) ;
while ( ! token . IsCancellationRequested )
{
await writer . YieldAsync ( frameCount ++ ) ; // instead of `yield return`
await UniTask . Yield ( ) ;
}
} ) ;
}
所有UGUI组件都实现了***AsAsyncEnumerable
可转换异步事件流。
async UniTask TripleClick ( )
{
// In default, used button.GetCancellationTokenOnDestroy to manage lieftime of async
await button . OnClickAsync ( ) ;
await button . OnClickAsync ( ) ;
await button . OnClickAsync ( ) ;
Debug . Log ( " Three times clicked " ) ;
}
// more efficient way
async UniTask TripleClick ( )
{
using ( var handler = button . GetAsyncClickEventHandler ( ) )
{
await handler . OnClickAsync ( ) ;
await handler . OnClickAsync ( ) ;
await handler . OnClickAsync ( ) ;
Debug . Log ( " Three times clicked " ) ;
}
}
// use async LINQ
async UniTask TripleClick ( CancellationToken token )
{
await button . OnClickAsAsyncEnumerable ( ) . Take ( 3 ) . Last ( ) ;
Debug . Log ( " Three times clicked " ) ;
}
// use async LINQ2
async UniTask TripleClick ( CancellationToken token )
{
await button . OnClickAsAsyncEnumerable ( ) . Take ( 3 ) . ForEachAsync ( _ =>
{
Debug . Log ( " Every clicked " ) ;
} ) ;
Debug . Log ( " Three times clicked, complete. " ) ;
}
所有monobehaviour消息事件都可以通过使用cysharp.threading.tasks.triggers启用异步来转换异步流的AsyncTriggers
流using Cysharp.Threading.Tasks.Triggers;
。可以使用GetAsync***Trigger
创建异步,并触发自己作为unitaskAsyncenumerable。
var trigger = this . GetOnCollisionEnterAsyncHandler ( ) ;
await trigger . OnCollisionEnterAsync ( ) ;
await trigger . OnCollisionEnterAsync ( ) ;
await trigger . OnCollisionEnterAsync ( ) ;
// every moves.
await this . GetAsyncMoveTrigger ( ) . ForEachAsync ( axisEventData =>
{
} ) ;
AsyncReactiveProperty
, AsyncReadOnlyReactiveProperty
是Unitask的反应性Property版本。 IUniTaskAsyncEnumerable<T>
的BindTo
扩展方法<t>用于绑定异步流值与统一组件(文本/可选/TMP/TEXT)。
var rp = new AsyncReactiveProperty < int > ( 99 ) ;
// AsyncReactiveProperty itself is IUniTaskAsyncEnumerable, you can query by LINQ
rp . ForEachAsync ( x =>
{
Debug . Log ( x ) ;
} , this . GetCancellationTokenOnDestroy ( ) ) . Forget ( ) ;
rp . Value = 10 ; // push 10 to all subscriber
rp . Value = 11 ; // push 11 to all subscriber
// WithoutCurrent ignore initial value
// BindTo bind stream value to unity components.
rp . WithoutCurrent ( ) . BindTo ( this . textComponent ) ;
await rp . WaitAsync ( ) ; // wait until next value set
// also exists ToReadOnlyAsyncReactiveProperty
var rp2 = new AsyncReactiveProperty < int > ( 99 ) ;
var rorp = rp . CombineLatest ( rp2 , ( x , y ) => ( x , y ) ) . ToReadOnlyAsyncReactiveProperty ( CancellationToken . None ) ;
直到序列中的异步处理完成之前,吸水型异步流才能获得下一个值。这可能会从按钮等按钮类型事件(例如按钮)溢出数据。
// can not get click event during 3 seconds complete.
await button . OnClickAsAsyncEnumerable ( ) . ForEachAwaitAsync ( async x =>
{
await UniTask . Delay ( TimeSpan . FromSeconds ( 3 ) ) ;
} ) ;
它很有用(防止双击),但有时没有用。
使用Queue()
方法还将在异步处理过程中排队事件。
// queued message in asynchronous processing
await button . OnClickAsAsyncEnumerable ( ) . Queue ( ) . ForEachAwaitAsync ( async x =>
{
await UniTask . Delay ( TimeSpan . FromSeconds ( 3 ) ) ;
} ) ;
或使用Subscribe
,开火和忘记样式。
button . OnClickAsAsyncEnumerable ( ) . Subscribe ( async x =>
{
await UniTask . Delay ( TimeSpan . FromSeconds ( 3 ) ) ;
} ) ;
Channel
与system.threading.tasks.channels相同,该通道类似于Golang通道。
目前,它仅支持多生产者单官量无界的通道。它可以通过Channel.CreateSingleConsumerUnbounded<T>()
创建。
对于生产者( .Writer
),请使用TryWrite
推动值,然后TryComplete
完成频道。对于消费者( .Reader
),请使用TryRead
, WaitToReadAsync
, ReadAsync
, Completion
和ReadAllAsync
读取排队的消息。
ReadAllAsync
返回IUniTaskAsyncEnumerable<T>
SO查询Linq运算符。阅读器仅允许单算物,但使用.Publish()
查询操作员启用多播消息。例如,制作酒吧/子实用程序。
public class AsyncMessageBroker < T > : IDisposable
{
Channel < T > channel ;
IConnectableUniTaskAsyncEnumerable < T > multicastSource ;
IDisposable connection ;
public AsyncMessageBroker ( )
{
channel = Channel . CreateSingleConsumerUnbounded < T > ( ) ;
multicastSource = channel . Reader . ReadAllAsync ( ) . Publish ( ) ;
connection = multicastSource . Connect ( ) ; // Publish returns IConnectableUniTaskAsyncEnumerable.
}
public void Publish ( T value )
{
channel . Writer . TryWrite ( value ) ;
}
public IUniTaskAsyncEnumerable < T > Subscribe ( )
{
return multicastSource ;
}
public void Dispose ( )
{
channel . Writer . TryComplete ( ) ;
connection . Dispose ( ) ;
}
}
Unity 6介绍了期待的类型,等待。简而言之,可以将等待的人视为一部分unitask,实际上,等待的设计受unitask的影响。它应该能够以类似的方式处理基于PlayerLoop的等待,汇总任务并支持取消CancellationToken
。由于它包含在标准库中,您可能会想知道是继续使用unitask还是迁移到等待。这是一个简短的指南。
首先,等待的功能等同于Coroutines提供的功能。您没有yield return
,而是要等待; await NextFrameAsync()
替代yield return null
; WaitForSeconds
和EndOfFrame
的等效物。但是,这就是它的程度。就功能而言,基于Coroutine,它缺乏基于任务的功能。在使用异步/等待的实际应用开发中,诸如WhenAll
必不可少的操作。此外,Unitask启用许多基于框架的操作(例如DelayFrame
)和更灵活的PlayerLooptiming Control,这在等待中不可用。当然,也没有跟踪器窗口。
因此,我建议将Unitask用于应用程序开发。 Unitask是等待的超集,并包含许多基本功能。对于图书馆开发,您想避免外部依赖关系,将等待作为方法的返回类型是适当的。可以使用AsUniTask
转换为unitask,因此处理Unitask库中的基于等待的功能没有问题。当然,如果您不必担心依赖关系,那么即使对于图书馆开发来说,使用Unitask也是最佳选择。
Unity的[UnityTest]
属性可以测试Coroutine(Ienumerator),但无法测试异步。 UniTask.ToCoroutine
桥异步/等待coroutine,因此您可以测试异步方法。
[ UnityTest ]
public IEnumerator DelayIgnore ( ) => UniTask . ToCoroutine ( async ( ) =>
{
var time = Time . realtimeSinceStartup ;
Time . timeScale = 0.5f ;
try
{
await UniTask . Delay ( TimeSpan . FromSeconds ( 3 ) , ignoreTimeScale : true ) ;
var elapsed = Time . realtimeSinceStartup - time ;
Assert . AreEqual ( 3 , ( int ) Math . Round ( TimeSpan . FromSeconds ( elapsed ) . TotalSeconds , MidpointRounding . ToEven ) ) ;
}
finally
{
Time . timeScale = 1.0f ;
}
} ) ;
Unitask自己的单元测试是使用Unity Test Runner和CYSHARP/RUNTIMENITTESTTOOLKIT编写的,以与CI集成,并检查IL2CPP是否有效。
大多数unitask方法都在单个线程(PlayerLoop)上运行,仅具有UniTask.Run
( Task.Run
oporcorent)和UniTask.SwitchToThreadPool
在线程池上运行。如果您使用线程池,则它将与WebGL一起使用,依此类推。
UniTask.Run
现在已弃用。您可以使用UniTask.RunOnThreadPool
。还要考虑您是否可以使用UniTask.Create
或UniTask.Void
。
您可以将Coroutine(Ienumerator)转换为unitask(或直接等待),但它有一些局限性。
WaitForEndOfFrame
/ WaitForFixedUpdate
/ Coroutine
。StartCoroutine
不同,它使用指定的PlayerLoopTiming
和默认的PlayerLoopTiming.Update
在Monobehaviour的Update
和StartCoroutine
的循环之前运行。如果您想完全兼容从coroutine到异步,请使用IEnumerator.ToUniTask(MonoBehaviour coroutineRunner)
过载。它在参数monobehaviour的实例上执行StartCoroutine,并等待在Unitask中完成。
Unitask可以像编辑Coroutine一样在Unity编辑器上运行。但是,有一些局限性。
DelayType.Realtime
等待合适的时间。EditorApplication.update
上运行。-batchmode
with -quit
不起作用,因为Unity不运行EditorApplication.update
并在单个帧之后退出。相反,请勿使用EditorApplication.Exit(0)
手动使用-quit
和quit。 Unitask具有许多标准的任务状API。该表显示了替代API是什么。
使用标准类型。
.NET类型 | unitask类型 |
---|---|
IProgress<T> | --- |
CancellationToken | --- |
CancellationTokenSource | --- |
使用Unitask类型。
.NET类型 | unitask类型 |
---|---|
Task / ValueTask | UniTask |
Task<T> / ValueTask<T> | UniTask<T> |
async void | async UniTaskVoid |
+= async () => { } | UniTask.Void , UniTask.Action , UniTask.UnityAction |
--- | UniTaskCompletionSource |
TaskCompletionSource<T> | UniTaskCompletionSource<T> / AutoResetUniTaskCompletionSource<T> |
ManualResetValueTaskSourceCore<T> | UniTaskCompletionSourceCore<T> |
IValueTaskSource | IUniTaskSource |
IValueTaskSource<T> | IUniTaskSource<T> |
ValueTask.IsCompleted | UniTask.Status.IsCompleted() |
ValueTask<T>.IsCompleted | UniTask<T>.Status.IsCompleted() |
new Progress<T> | Progress.Create<T> |
CancellationToken.Register(UnsafeRegister) | CancellationToken.RegisterWithoutCaptureExecutionContext |
CancellationTokenSource.CancelAfter | CancellationTokenSource.CancelAfterSlim |
Channel.CreateUnbounded<T>(false){ SingleReader = true } | Channel.CreateSingleConsumerUnbounded<T> |
IAsyncEnumerable<T> | IUniTaskAsyncEnumerable<T> |
IAsyncEnumerator<T> | IUniTaskAsyncEnumerator<T> |
IAsyncDisposable | IUniTaskAsyncDisposable |
Task.Delay | UniTask.Delay |
Task.Yield | UniTask.Yield |
Task.Run | UniTask.RunOnThreadPool |
Task.WhenAll | UniTask.WhenAll |
Task.WhenAny | UniTask.WhenAny |
Task.WhenEach | UniTask.WhenEach |
Task.CompletedTask | UniTask.CompletedTask |
Task.FromException | UniTask.FromException |
Task.FromResult | UniTask.FromResult |
Task.FromCanceled | UniTask.FromCanceled |
Task.ContinueWith | UniTask.ContinueWith |
TaskScheduler.UnobservedTaskException | UniTaskScheduler.UnobservedTaskException |
unitask积极地缓存异步承诺对象实现零分配(有关技术详细信息,请参见博客文章unitask v2 - 零分配异步/等待unity,以及异步的linq)。默认情况下,它缓存了所有承诺,但是您可以将TaskPool.SetMaxPoolSize
配置为您的值,该值表示每种类型的高速缓存大小。 TaskPool.GetCacheSizeInfo
返回当前缓存的对象。
foreach ( var ( type , size ) in TaskPool . GetCacheSizeInfo ( ) )
{
Debug . Log ( type + " : " + size ) ;
}
在Unityeditor中,Profiler显示了编译器生成异步的分配,但仅发生在调试(开发)构建中。 C#编译器生成了Asyncstatemachine作为DEBUG构建中的类,并在发行版本上作为结构。
Unity从2020.1开始支持代码优化选项(右,页脚)。
您可以更改C#编译器优化以释放以删除开发构建中的Asyncstatemachine分配。也可以通过Compilation.CompilationPipeline-codeOptimization
设置Compilation.CodeOptimization
优化选项。
Unity的默认同步性电信( UnitySynchronizationContext
)是性能的差。 unitask绕过SynchronizationContext
(和ExecutionContext
),因此它不使用它,但是如果在async Task
中存在,则仍然使用它。 UniTaskSynchronizationContext
是UnitySynchronizationContext
的替代,这是更好的性能。
public class SyncContextInjecter
{
[ RuntimeInitializeOnLoadMethod ( RuntimeInitializeLoadType . SubsystemRegistration ) ]
public static void Inject ( )
{
SynchronizationContext . SetSynchronizationContext ( new UniTaskSynchronizationContext ( ) ) ;
}
}
这是一个可选的选择,并不总是建议; UniTaskSynchronizationContext
性能低于async UniTask
,并且不是完整的unitask替换。它还不能保证与UnitySynchronizationContext
的完全行为兼容性。
Unitask的API参考文献由DOCFX和CYSHARP/DOCFXTEMPLATE托管在cysharp.github.io/unitask上。
例如,可以在Unitask#方法上看到Unitask的工厂方法。可以在unitaskAsyncenumerable#方法上看到unitaskAsyncenumerable的工厂/扩展方法。
需要一个统一版本,该版本支持GIT软件包的路径查询参数(Unity> = 2019.3.4f1,Unity> = 2020.1A21)。您可以添加https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask
到包装管理器
或添加"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask"
Packages/manifest.json
如果要设置目标版本,unitask使用*.*.*
释放标签,以便指定#2.1.0
之类的版本。例如https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#2.1.0
。
对于.NET核心,请使用Nuget。
pm>安装包装一式装置
.NET CORE版本的Unitask是Unity Unitass的子集,并删除了PlayerLoop依赖性方法。
它以高于标准任务/ValueTask的性能运行,但是在使用时,您应该小心忽略ExecutionContext/SynchronizationContext。 AsyncLocal
也不起作用,因为它忽略了executionContext。
如果您在内部使用Unitask,但将ValueTask作为外部API提供,则可以像以下内容一样编写(受Pomedawait的启发)。
public class ZeroAllocAsyncAwaitInDotNetCore
{
public ValueTask < int > DoAsync ( int x , int y )
{
return Core ( this , x , y ) ;
static async UniTask < int > Core ( ZeroAllocAsyncAwaitInDotNetCore self , int x , int y )
{
// do anything...
await Task . Delay ( TimeSpan . FromSeconds ( x + y ) ) ;
await UniTask . Yield ( ) ;
return 10 ;
}
}
}
// UniTask does not return to original SynchronizationContext but you can use helper `ReturnToCurrentSynchronizationContext`.
public ValueTask TestAsync ( )
{
await using ( UniTask . ReturnToCurrentSynchronizationContext ( ) )
{
await UniTask . SwitchToThreadPool ( ) ;
// do anything..
}
}
.NET Core版本旨在允许用户在与Unity共享代码时使用Unitask作为接口(例如Cysharp/Magiconion)。 。
提供等同于unitask的何时提供的实用方法作为cysharp/valueTaskSuppplement。
该图书馆属于麻省理工学院许可证。