Compare commits

..

3 Commits

Author SHA1 Message Date
Masatake Iwasaki c01f3096db
Merge d9f55b40a5 into b0d01ca75f 2024-12-13 08:02:45 +01:00
Yoshifumi Kawai b0d01ca75f
Merge pull request #641 from hmkc/dev
Update README_CN.md
2024-12-12 16:35:32 +09:00
hmkc 7a63ab7088 Update README_CN.md
Re-translated the documentation.
2024-12-11 17:13:32 +08:00
1 changed files with 207 additions and 208 deletions

View File

@ -2,28 +2,27 @@ UniTask
=== ===
[![GitHub Actions](https://github.com/Cysharp/UniTask/workflows/Build-Debug/badge.svg)](https://github.com/Cysharp/UniTask/actions) [![Releases](https://img.shields.io/github/release/Cysharp/UniTask.svg)](https://github.com/Cysharp/UniTask/releases) [![GitHub Actions](https://github.com/Cysharp/UniTask/workflows/Build-Debug/badge.svg)](https://github.com/Cysharp/UniTask/actions) [![Releases](https://img.shields.io/github/release/Cysharp/UniTask.svg)](https://github.com/Cysharp/UniTask/releases)
为Unity提供一个高性能0GC的async/await异步方案。 为Unity提供一个高性能零堆内存分配的 async/await 异步方案。
- 基于值类型的`UniTask<T>`和自定义的 AsyncMethodBuilder 来实现0GC - 基于值类型的`UniTask<T>`和自定义的 AsyncMethodBuilder 来实现零堆内存分配
- 使所有 Unity 的 AsyncOperations 和 Coroutines 可等待 - 使所有 Unity 的 AsyncOperations 和 Coroutines 可等待
- 基于 PlayerLoop 的任务(`UniTask.Yield``UniTask.Delay``UniTask.DelayFrame`等..) 可以替换所有协程操作 - 基于 PlayerLoop 的任务`UniTask.Yield``UniTask.Delay``UniTask.DelayFrame`等..可以替换所有协程操作
- 对MonoBehaviour 消息事件和 uGUI 事件进行 可等待/异步枚举 拓 - 对 MonoBehaviour 消息事件和 uGUI 事件进行可等待/异步枚举扩
- 完全在 Unity 的 PlayerLoop 上运行因此不使用Thread并且同样能在 WebGL、wasm 等平台上运行。 - 完全在 Unity 的 PlayerLoop 上运行因此不使用Thread并且同样能在 WebGL、wasm 等平台上运行。
- 带有 Channel 和 AsyncReactiveProperty的异步 LINQ - 带有 Channel 和 AsyncReactiveProperty 的异步 LINQ
- 提供一个 TaskTracker EditorWindow 以追踪所有UniTask分配来预防内存泄漏 - 提供一个 TaskTracker EditorWindow 以追踪所有 UniTask 分配来预防内存泄漏
- 与原生 Task/ValueTask/IValueTaskSource 高度兼容的行为 - 与原生 Task/ValueTask/IValueTaskSource 高度兼容的行为
有关技术细节,请参阅博客文章:[UniTask v2 — Unity 的0GC async/await 以及 异步LINQ 的使用 有关技术细节,请参阅博客文章:[UniTask v2 — 适用于 Unity 的零堆内存分配的async/await支持异步 LINQ](https://medium.com/@neuecc/unitask-v2-zero-allocation-async-await-for-unity-with-asynchronous-linq-1aa9c96aa7dd)
](https://medium.com/@neuecc/unitask-v2-zero-allocation-async-await-for-unity-with-asynchronous-linq-1aa9c96aa7dd)
有关高级技巧,请参阅博客文章:[通过异步装饰器模式扩展 UnityWebRequest — UniTask 的高级技术](https://medium.com/@neuecc/extends-unitywebrequest-via-async-decorator-pattern-advanced-techniques-of-unitask-ceff9c5ee846) 有关高级技巧,请参阅博客文章:[通过异步装饰器模式扩展 UnityWebRequest — UniTask 的高级技术](https://medium.com/@neuecc/extends-unitywebrequest-via-async-decorator-pattern-advanced-techniques-of-unitask-ceff9c5ee846)
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table of Contents ## 目录
- [入门](#%E5%85%A5%E9%97%A8) - [入门](#%E5%85%A5%E9%97%A8)
- [UniTask 和 AsyncOperation 基础知识](#unitask-%E5%92%8C-asyncoperation-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86) - [UniTask 和 AsyncOperation 基础知识](#unitask-%E5%92%8C-asyncoperation-%E7%9A%84%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86)
- [Cancellation and Exception 处理](#cancellation-and-exception-handling) - [取消和异常处理](#%E5%8F%96%E6%B6%88%E5%92%8C%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86)
- [超时处理](#%E8%B6%85%E6%97%B6%E5%A4%84%E7%90%86) - [超时处理](#%E8%B6%85%E6%97%B6%E5%A4%84%E7%90%86)
- [进度](#%E8%BF%9B%E5%BA%A6) - [进度](#%E8%BF%9B%E5%BA%A6)
- [PlayerLoop](#playerloop) - [PlayerLoop](#playerloop)
@ -35,43 +34,43 @@ UniTask
- [Channel](#channel) - [Channel](#channel)
- [与 Awaitable 对比](#%E4%B8%8E-awaitable-%E5%AF%B9%E6%AF%94) - [与 Awaitable 对比](#%E4%B8%8E-awaitable-%E5%AF%B9%E6%AF%94)
- [单元测试](#%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95) - [单元测试](#%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95)
- [线程池限制](#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E9%99%90%E5%88%B6) - [线程池限制](#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E9%99%90%E5%88%B6)
- [IEnumerator.ToUniTask 限制](#ienumeratortounitask-%E9%99%90%E5%88%B6) - [IEnumerator.ToUniTask 限制](#ienumeratortounitask-%E7%9A%84%E9%99%90%E5%88%B6)
- [关于UnityEditor](#%E5%85%B3%E4%BA%8Eunityeditor) - [关于 UnityEditor](#%E5%85%B3%E4%BA%8E-unityeditor)
- [与原生Task API对比](#%E4%B8%8E%E5%8E%9F%E7%94%9Ftask-api%E5%AF%B9%E6%AF%94) - [与原生 Task API 对比](#%E4%B8%8E%E5%8E%9F%E7%94%9F-task-api-%E5%AF%B9%E6%AF%94)
- [池化配置](#%E6%B1%A0%E5%8C%96%E9%85%8D%E7%BD%AE) - [池化配置](#%E6%B1%A0%E5%8C%96%E9%85%8D%E7%BD%AE)
- [Profiler下的分配](#profiler%E4%B8%8B%E7%9A%84%E5%88%86%E9%85%8D) - [Profiler 下的堆内存分配](#profiler-%E4%B8%8B%E7%9A%84%E5%A0%86%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D)
- [UniTaskSynchronizationContext](#unitasksynchronizationcontext) - [UniTaskSynchronizationContext](#unitasksynchronizationcontext)
- [API References](#api-references) - [API 文档](#api-%E6%96%87%E6%A1%A3)
- [UPM Package](#upm-package) - [UPM 包](#upm-%E5%8C%85)
- [通过 git URL 安装](#%E9%80%9A%E8%BF%87-git-url-%E5%AE%89%E8%A3%85) - [通过 git URL 安装](#%E9%80%9A%E8%BF%87-git-url-%E5%AE%89%E8%A3%85)
- [.NET Core](#net-core) - [关于 .NET Core](#%E5%85%B3%E4%BA%8E-net-core)
- [License](#license) - [许可证](#%E8%AE%B8%E5%8F%AF%E8%AF%81)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
入门 入门
--- ---
通过[UniTask/releases](https://github.com/Cysharp/UniTask/releases)页面中提供的[UPM 包](https://github.com/Cysharp/UniTask#upm-package)或资产包 ( `UniTask.*.*.*.unitypackage`)安装。 通过[UniTask/releases](https://github.com/Cysharp/UniTask/releases)页面中提供的[UPM 包](https://github.com/Cysharp/UniTask#upm-package)或资产包`UniTask.*.*.*.unitypackage`安装。
```csharp ```csharp
// 使用UniTask所需的命名空间 // 使用 UniTask 所需的命名空间
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
// 可以返回一个形如 UniTask<T>(或 UniTask) 的类型这种类型事为Unity定制的作为替代原生Task<T>的轻量级方案 // 可以返回一个形如 UniTask<T>(或 UniTask) 的类型这种类型事为Unity定制的作为替代原生 Task<T> 的轻量级方案
// 为Unity集成的 0GC快速调用0消耗的 async/await 方案 // 为 Unity 集成的零堆内存分配快速调用0消耗的 async/await 方案
async UniTask<string> DemoAsync() async UniTask<string> DemoAsync()
{ {
// 你可以等待一个Unity异步对象 // 您可以等待一个 Unity 异步对象
var asset = await Resources.LoadAsync<TextAsset>("foo"); var asset = await Resources.LoadAsync<TextAsset>("foo");
var txt = (await UnityWebRequest.Get("https://...").SendWebRequest()).downloadHandler.text; var txt = (await UnityWebRequest.Get("https://...").SendWebRequest()).downloadHandler.text;
await SceneManager.LoadSceneAsync("scene2"); await SceneManager.LoadSceneAsync("scene2");
// .WithCancellation 会启用取消功能GetCancellationTokenOnDestroy 表示获取一个依赖对象生命周期的Cancel句柄当对象被销毁时将会调用这个Cancel句柄从而实现取消的功能 // .WithCancellation 会启用取消功能GetCancellationTokenOnDestroy 表示获取一个依赖对象生命周期的 Cancel 句柄,当对象被销毁时,将会调用这个 Cancel 句柄,从而实现取消的功能
// 在Unity 2022.2之后你可以在MonoBehaviour中使用`destroyCancellationToken` // 在 Unity 2022.2之后,您可以在 MonoBehaviour 中使用`destroyCancellationToken`
var asset2 = await Resources.LoadAsync<TextAsset>("bar").WithCancellation(this.GetCancellationTokenOnDestroy()); var asset2 = await Resources.LoadAsync<TextAsset>("bar").WithCancellation(this.GetCancellationTokenOnDestroy());
// .ToUniTask 可接收一个 progress 回调以及一些配置参数Progress.Create是IProgress<T>的轻量级替代方案 // .ToUniTask 可接收一个 progress 回调以及一些配置参数Progress.Create IProgress<T> 的轻量级替代方案
var asset3 = await Resources.LoadAsync<TextAsset>("baz").ToUniTask(Progress.Create<float>(x => Debug.Log(x))); var asset3 = await Resources.LoadAsync<TextAsset>("baz").ToUniTask(Progress.Create<float>(x => Debug.Log(x)));
// 等待一个基于帧的延时操作(就像一个协程一样) // 等待一个基于帧的延时操作(就像一个协程一样)
@ -80,34 +79,34 @@ async UniTask<string> DemoAsync()
// yield return new WaitForSeconds/WaitForSecondsRealtime 的替代方案 // yield return new WaitForSeconds/WaitForSecondsRealtime 的替代方案
await UniTask.Delay(TimeSpan.FromSeconds(10), ignoreTimeScale: false); await UniTask.Delay(TimeSpan.FromSeconds(10), ignoreTimeScale: false);
// 可以等待任何 playerloop 的生命周期(PreUpdateUpdateLateUpdate等...) // 可以等待任何 playerloop 的生命周期PreUpdateUpdateLateUpdate等
await UniTask.Yield(PlayerLoopTiming.PreLateUpdate); await UniTask.Yield(PlayerLoopTiming.PreLateUpdate);
// yield return null 替代方案 // yield return null 替代方案
await UniTask.Yield(); await UniTask.Yield();
await UniTask.NextFrame(); await UniTask.NextFrame();
// WaitForEndOfFrame 替代方案 // WaitForEndOfFrame 替代方案
#if UNITY_2023_1_OR_NEWER #if UNITY_2023_1_OR_NEWER
await UniTask.WaitForEndOfFrame(); await UniTask.WaitForEndOfFrame();
#else #else
// 需要 MonoBehaviour(CoroutineRunner) // 需要 MonoBehaviourCoroutineRunner
await UniTask.WaitForEndOfFrame(this); // this是一个 MonoBehaviour await UniTask.WaitForEndOfFrame(this); // this是一个 MonoBehaviour
#endif #endif
// yield return new WaitForFixedUpdate 替代方案,(和 UniTask.Yield(PlayerLoopTiming.FixedUpdate) 效果一样) // yield return new WaitForFixedUpdate 的替代方案,(等同于 UniTask.Yield(PlayerLoopTiming.FixedUpdate)
await UniTask.WaitForFixedUpdate(); await UniTask.WaitForFixedUpdate();
// yield return WaitUntil 替代方案 // yield return WaitUntil 替代方案
await UniTask.WaitUntil(() => isActive == false); await UniTask.WaitUntil(() => isActive == false);
// WaitUntil展,指定某个值改变时触发 // WaitUntil展,指定某个值改变时触发
await UniTask.WaitUntilValueChanged(this, x => x.isActive); await UniTask.WaitUntilValueChanged(this, x => x.isActive);
// 可以直接 await 一个 IEnumerator 协程 // 可以直接 await 一个 IEnumerator 协程
await FooCoroutineEnumerator(); await FooCoroutineEnumerator();
// 可以直接 await 一个原生 task // 可以直接 await 一个原生 task
await Task.Run(() => 100); await Task.Run(() => 100);
// 多线程示例,在此行代码后的内容都运行在一个线程池上 // 多线程示例,在此行代码后的内容都运行在一个线程池上
@ -115,7 +114,7 @@ async UniTask<string> DemoAsync()
/* 工作在线程池上的代码 */ /* 工作在线程池上的代码 */
// 转回主线程(和UniRx中的`ObserveOnMainThread`效果一样 // 转回主线程(等同于 UniRx 的`ObserveOnMainThread`
await UniTask.SwitchToMainThread(); await UniTask.SwitchToMainThread();
// 获取异步的 webrequest // 获取异步的 webrequest
@ -129,24 +128,24 @@ async UniTask<string> DemoAsync()
var task2 = GetTextAsync(UnityWebRequest.Get("http://bing.com")); var task2 = GetTextAsync(UnityWebRequest.Get("http://bing.com"));
var task3 = GetTextAsync(UnityWebRequest.Get("http://yahoo.com")); var task3 = GetTextAsync(UnityWebRequest.Get("http://yahoo.com"));
// 构造一个async-wait并通过元组语义轻松获取所有结果 // 构造一个 async-wait并通过元组语义轻松获取所有结果
var (google, bing, yahoo) = await UniTask.WhenAll(task1, task2, task3); var (google, bing, yahoo) = await UniTask.WhenAll(task1, task2, task3);
// WhenAll简写形式元组可以直接await // WhenAll简写形式,元组可以直接 await
var (google2, bing2, yahoo2) = await (task1, task2, task3); var (google2, bing2, yahoo2) = await (task1, task2, task3);
// 返回一个异步值,或者你也可以使用`UniTask`(无结果)`UniTaskVoid`(协程,不可等待) // 返回一个异步值,或者您也可以使用`UniTask`(无结果),`UniTaskVoid`(不可等待)
return (asset as TextAsset)?.text ?? throw new InvalidOperationException("Asset not found"); return (asset as TextAsset)?.text ?? throw new InvalidOperationException("Asset not found");
} }
``` ```
UniTask 和 AsyncOperation 基础知识 UniTask 和 AsyncOperation 基础知识
--- ---
UniTask 功能依赖于 C# 7.0( [task-like custom async method builder feature](https://github.com/dotnet/roslyn/blob/master/docs/features/task-types.md) ) 所以需要的 Unity 最低版本是`Unity 2018.3` ,官方支持的最低版本是`Unity 2018.4.13f1`. UniTask 功能依赖于 C# 7.0[task-like custom async method builder feature](https://github.com/dotnet/roslyn/blob/master/docs/features/task-types.md)),所以需要`Unity 2018.3`之后的版本,官方支持的最低版本是`Unity 2018.4.13f1`。
为什么需要 UniTask自定义task对象因为原生 Task 太重,与 Unity 线程单线程相性不好。UniTask 不使用线程和 SynchronizationContext/ExecutionContext,因为 Unity 的异步对象由 Unity 的引擎层自动调度。它实现了更快和更低的分配并且与Unity完全兼容。 为什么需要 UniTask自定义task对象因为原生 Task 太重,与 Unity 线程(单线程)相性不好。因为 Unity 的异步对象由 Unity 的引擎层自动调度,所以 UniTask 不使用线程和 SynchronizationContext/ExecutionContext。它实现了更快和更低的分配并且与Unity完全兼容。
你可以在使用 `using Cysharp.Threading.Tasks;`时对 `AsyncOperation` `ResourceRequest``AssetBundleRequest` `AssetBundleCreateRequest` `UnityWebRequestAsyncOperation` `AsyncGPUReadbackRequest` `IEnumerator`以及其他的异步操作进行 await 您可以在使用`using Cysharp.Threading.Tasks;`时对`AsyncOperation``ResourceRequest``AssetBundleRequest``AssetBundleCreateRequest``UnityWebRequestAsyncOperation``AsyncGPUReadbackRequest``IEnumerator`以及其他的异步操作进行 await
UniTask 提供了三种模式的扩展方法。 UniTask 提供了三种模式的扩展方法。
@ -156,13 +155,13 @@ await asyncOperation;
.ToUniTask(IProgress, PlayerLoopTiming, CancellationToken); .ToUniTask(IProgress, PlayerLoopTiming, CancellationToken);
``` ```
`WithCancellation`是`ToUniTask`的简化版本,两者都返回`UniTask`。有关cancellation的详细信息请参阅[取消和异常处理](https://github.com/Cysharp/UniTask#cancellation-and-exception-handling)部分。 `WithCancellation`是`ToUniTask`的简化版本,两者都返回`UniTask`。有关 cancellation 的详细信息,请参阅:[取消和异常处理](https://github.com/Cysharp/UniTask#cancellation-and-exception-handling)部分。
> 注意await 会在 PlayerLoop 执行await对象的相应native生命周期方法时返回如果条件满足的话而 WithCancellation 和 ToUniTask 是从指定的 PlayerLoop 生命周期执行时返回。有关 PlayLoop生命周期 的详细信息,请参阅:[PlayerLoop](https://github.com/Cysharp/UniTask#playerloop)部分。 > 注意await 会在 PlayerLoop 执行await对象的相应native生命周期方法时返回如果条件满足的话而 WithCancellation 和 ToUniTask 是从指定的 PlayerLoop 生命周期执行时返回。有关 PlayLoop生命周期 的详细信息,请参阅:[PlayerLoop](https://github.com/Cysharp/UniTask#playerloop)部分。
> 注意: AssetBundleRequest 有`asset`和`allAssets`,默认 await 返回`asset`。如果你想得到`allAssets`,你可以使用`AwaitForAllAssets()`方法。 > 注意: AssetBundleRequest 有`asset`和`allAssets`,默认 await 返回`asset`。如果您想得到`allAssets`,您可以使用`AwaitForAllAssets()`方法。
`UniTask`可以使用`UniTask.WhenAll`和`UniTask.WhenAny`等实用函数。它们就像`Task.WhenAll``Task.WhenAny``UniTask.WhenEach`。但它们会返回内容,这很有用。它们会返回值元组,因此您可以传递多种类型并解构每个结果。 `UniTask`可以使用`UniTask.WhenAll``UniTask.WhenAny``UniTask.WhenEach`等实用函数。它们就像`Task.WhenAll`和`Task.WhenAny`,但它们返回的数据类型更好用。它们会返回值元组,因此您可以传递多种类型并解构每个结果。
```csharp ```csharp
public async UniTaskVoid LoadManyAsync() public async UniTaskVoid LoadManyAsync()
@ -181,7 +180,7 @@ async UniTask<Sprite> LoadAsSprite(string path)
} }
``` ```
如果你想转换一个回调逻辑块让它变成UniTask的话可以使用 `UniTaskCompletionSource<T>` `TaskCompletionSource<T>`的轻量级魔改版) 如果您想要将一个回调转换为 UniTask您可以使用`UniTaskCompletionSource<T>`,它是`TaskCompletionSource<T>`的轻量级版本。
```csharp ```csharp
public UniTask<int> WrapByUniTaskCompletionSource() public UniTask<int> WrapByUniTaskCompletionSource()
@ -192,40 +191,40 @@ public UniTask<int> WrapByUniTaskCompletionSource()
// 当操作失败时,调用 utcs.TrySetException(); // 当操作失败时,调用 utcs.TrySetException();
// 当操作取消时,调用 utcs.TrySetCanceled(); // 当操作取消时,调用 utcs.TrySetCanceled();
return utcs.Task; //本质上就是返回了一个UniTask<int> return utcs.Task; //本质上就是返回了一个 UniTask<int>
} }
``` ```
您可以进行如下转换<br>-`Task` -> `UniTask `: 使用`AsUniTask`<br>-`UniTask` -> `UniTask<AsyncUnit>`: 使用 `AsAsyncUnitUniTask`<br>-`UniTask<T>` -> `UniTask`: 使用 `AsUniTask`。`UniTask<T>` -> `UniTask`的转换是无消耗的。 您可以进行如下转换<br>-`Task` -> `UniTask `使用`AsUniTask`<br>-`UniTask` -> `UniTask<AsyncUnit>`使用 `AsAsyncUnitUniTask`<br>-`UniTask<T>` -> `UniTask`使用 `AsUniTask`。`UniTask<T>` -> `UniTask`的转换是无消耗的。
如果你想将异步转换为协程,你可以使用`.ToCoroutine()`,如果你只想允许使用协程系统,这很有用 如果您想将异步转换为协程,您可以使用`.ToCoroutine()`,这对于您想只允许使用协程系统大有帮助
UniTask 不能await两次。这是与.NET Standard 2.1 中引入的[ValueTask/IValueTaskSource](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.valuetask-1?view=netcore-3.1)相同的约束。 UniTask 不能 await 两次。这是与.NET Standard 2.1 中引入的[ValueTask/IValueTaskSource](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.valuetask-1?view=netcore-3.1)具有相同的约束。
> 永远不应在 ValueTask<TResult> 实例上执行以下操作: > 千万不要对 `ValueTask<TResult>` 实例执行以下操作:
> >
> - 多次await实例。 > - 多次await实例。
> - 多次调用 AsTask。 > - 多次调用 AsTask。
> - 在操作尚未完成时调用 .Result 或 .GetAwaiter().GetResult(),多次调用也是不允许的 > - 在操作尚未完成时调用 .Result 或 .GetAwaiter().GetResult()或对它们进行多次调用。
> - 混用上述行为更是不被允许的 > - 对实例进行上述多种操作
> >
> 如果您执行上述任何操作,则结果是未定义。 > 如果您执行上述任何操作,则结果是未定义
```csharp ```csharp
var task = UniTask.DelayFrame(10); var task = UniTask.DelayFrame(10);
await task; await task;
await task; // 寄了,抛出异常 await task; // 错误,抛出异常
``` ```
如果实在需要多次await一个异步操作可以使用`UniTask.Lazy`来支持多次调用。`.Preserve()`同样允许多次调用由UniTask内部缓存的结果。这种方法在函数范围内有多个调用时很有用。 如果实在需要多次 await 一个异步操作,可以使用支持多次调用的`UniTask.Lazy`。`.Preserve()`同样允许多次调用(由 UniTask 内部缓存结果)。这种方法在函数范围内有多次调用时很有用。
同样的`UniTaskCompletionSource`可以在同一个地方被await多次或者在很多不同的地方被await。 同样的`UniTaskCompletionSource`可以在同一个地方被 await 多次,或者在很多不同的地方被 await。
Cancellation和Exception处理 取消和异常处理
--- ---
一些 UniTask 工厂方法有一个`CancellationToken cancellationToken = default`参数。Unity 的一些异步操作也有`WithCancellation(CancellationToken)`和`ToUniTask(..., CancellationToken cancellation = default)`展方法。 一些 UniTask 工厂方法有一个`CancellationToken cancellationToken = default`参数。Unity 的一些异步操作也有`WithCancellation(CancellationToken)`和`ToUniTask(..., CancellationToken cancellation = default)`展方法。
可以传递原生[`CancellationTokenSource`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource)给参数CancellationToken 可以通过原生的[`CancellationTokenSource`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource)将 CancellationToken 传递给参数
```csharp ```csharp
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
@ -240,14 +239,14 @@ await UnityWebRequest.Get("http://google.co.jp").SendWebRequest().WithCancellati
await UniTask.DelayFrame(1000, cancellationToken: cts.Token); await UniTask.DelayFrame(1000, cancellationToken: cts.Token);
``` ```
CancellationToken 可以由`CancellationTokenSource`或 MonoBehaviour 的`GetCancellationTokenOnDestroy`扩展方法创建。 CancellationToken 可通过`CancellationTokenSource`或 MonoBehaviour 的扩展方法`GetCancellationTokenOnDestroy`来创建。
```csharp ```csharp
// 这个CancellationTokenSource和this GameObject生命周期相同当this GameObject Destroy的时候就会执行Cancel // 这个 CancellationToken 的生命周期与 GameObject 的相同
await UniTask.DelayFrame(1000, cancellationToken: this.GetCancellationTokenOnDestroy()); await UniTask.DelayFrame(1000, cancellationToken: this.GetCancellationTokenOnDestroy());
``` ```
对于链式取消,所有异步方法都建议最后一个参数接受`CancellationToken cancellationToken`,并将`CancellationToken`从头传递到尾。 对于链式取消,建议所有异步方法的最后一个参数都接受`CancellationToken cancellationToken`,并将`CancellationToken`从头传递到尾。
```csharp ```csharp
await FooAsync(this.GetCancellationTokenOnDestroy()); await FooAsync(this.GetCancellationTokenOnDestroy());
@ -265,7 +264,7 @@ async UniTask BarAsync(CancellationToken cancellationToken)
} }
``` ```
`CancellationToken`表示异步的生命周期。您可以使用自定义的生命周期,而不是默认的 CancellationTokenOnDestroy `CancellationToken`代表了异步操作的生命周期。您可以不使用默认的 CancellationTokenOnDestroy ,通过自定义的`CancellationToken`自行管理生命周期
```csharp ```csharp
public class MyBehaviour : MonoBehaviour public class MyBehaviour : MonoBehaviour
@ -295,11 +294,11 @@ public class MyBehaviour : MonoBehaviour
} }
``` ```
在Unity 2022.2之后Unity在[MonoBehaviour.destroyCancellationToken](https://docs.unity3d.com/ScriptReference/MonoBehaviour-destroyCancellationToken.html)和[Application.exitCancellationToken](https://docs.unity3d.com/ScriptReference/Application-exitCancellationToken.html)中添加了CancellationToken。 在Unity 2022.2之后Unity在[MonoBehaviour.destroyCancellationToken](https://docs.unity3d.com/ScriptReference/MonoBehaviour-destroyCancellationToken.html)和[Application.exitCancellationToken](https://docs.unity3d.com/ScriptReference/Application-exitCancellationToken.html)中添加了 CancellationToken。
当检测到取消时,所有方法都会向上游抛出并传播`OperationCanceledException`。当异常(不限于`OperationCanceledException`)没有在异步方法中处理时,它将最终传播到`UniTaskScheduler.UnobservedTaskException`。接收到的未处理异常的默认行为是将日志写入异常。可以使用`UniTaskScheduler.UnobservedExceptionWriteLogType`更改日志级别。如果要使用自定义行为,请为`UniTaskScheduler.UnobservedTaskException.`设置一个委托 当检测到取消时,所有方法都会向上游抛出并传播`OperationCanceledException`。当异常(不限于`OperationCanceledException`)没有在异步方法中处理时,它将被传播到`UniTaskScheduler.UnobservedTaskException`。默认情况下,将接收到的未处理异常作为一般异常写入日志。可以使用`UniTaskScheduler.UnobservedExceptionWriteLogType`更改日志级别。若想对接收到未处理异常时的处理进行自定义,请为`UniTaskScheduler.UnobservedTaskException`设置一个委托
而`OperationCanceledException`是一个特殊的异常,会被`UnobservedTaskException`.无视 而`OperationCanceledException`是一种特殊的异常,会被`UnobservedTaskException`无视
如果要取消异步 UniTask 方法中的行为,请手动抛出`OperationCanceledException`。 如果要取消异步 UniTask 方法中的行为,请手动抛出`OperationCanceledException`。
@ -311,7 +310,7 @@ public async UniTask<int> FooAsync()
} }
``` ```
如果您处理异常但想忽略传播到全局cancellation处理的地方),请使用异常过滤器。 如果您只想处理异常,忽略取消操作(让其传播到全局处理 cancellation 的地方),请使用异常过滤器。
```csharp ```csharp
public async UniTask<int> BarAsync() public async UniTask<int> BarAsync()
@ -321,14 +320,14 @@ public async UniTask<int> BarAsync()
var x = await FooAsync(); var x = await FooAsync();
return x * 2; return x * 2;
} }
catch (Exception ex) when (!(ex is OperationCanceledException)) // when (ex is not OperationCanceledException) at C# 9.0 catch (Exception ex) when (!(ex is OperationCanceledException)) // 在 C# 9.0 下改成 when (ex is not OperationCanceledException)
{ {
return -1; return -1;
} }
} }
``` ```
throws/catch`OperationCanceledException`有点重,所以如果性能是一个问题,请使用`UniTask.SuppressCancellationThrow`以避免 OperationCanceledException 抛出。它将返回`(bool IsCanceled, T Result)`而不是抛出。 抛出和捕获`OperationCanceledException`有点重度,如果比较在意性能开销,请使用`UniTask.SuppressCancellationThrow`以避免抛出 OperationCanceledException 。它将返回`(bool IsCanceled, T Result)`而不是抛出异常
```csharp ```csharp
var (isCanceled, _) = await UniTask.DelayFrame(10, cancellationToken: cts.Token).SuppressCancellationThrow(); var (isCanceled, _) = await UniTask.DelayFrame(10, cancellationToken: cts.Token).SuppressCancellationThrow();
@ -338,19 +337,19 @@ if (isCanceled)
} }
``` ```
注意:仅当您在原方法直接调用SuppressCancellationThrow时才会抑制异常抛出。否则返回值将被转换且整个管道不会抑制 throws 注意:仅当您在源头处直接调用`UniTask.SuppressCancellationThrow`时才会抑制异常抛出。否则,返回值将被转换,且整个管道不会抑制异常抛出
`UniTask.Yield`和`UniTask.Delay`等功能依赖于Unity的player loop它们在player loop上确定`CancellationToken`状态。 `UniTask.Yield`和`UniTask.Delay`等功能依赖于 Unity 的 PlayerLoop它们在 PlayerLoop 中确定`CancellationToken`状态。
因此在player loop之外调用`UniTask.Yield`或`UniTask.Delay`不会立即取消。 这意味着当`CancellationToken`被触发时,它们并不会立即取消。
如果要更改此行为,想要立即取消,可将`cancelImmediately`标志设置为true。 如果要更改此行为,实现立即取消,可将`cancelImmediately`标志设置为 true。
```csharp ```csharp
await UniTask.Yield(cancellationToken, cancelImmediately: true); await UniTask.Yield(cancellationToken, cancelImmediately: true);
``` ```
Note: Setting `cancelImmediately` to true and detecting an immediate cancellation is more costly than the default behavior. 注意:比起默认行为,设置 `cancelImmediately` 为 true 并检测立即取消会有更多的性能开销。
This is because it uses `CancellationToken.Register`; it is heavier than checking CancellationToken on the player loop. 这是因为它使用了`CancellationToken.Register`;这比在 PlayerLoop 中检查 CancellationToken 更重度。
超时处理 超时处理
--- ---
@ -358,7 +357,7 @@ This is because it uses `CancellationToken.Register`; it is heavier than checkin
```csharp ```csharp
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
cts.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 5sec timeout. cts.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 设置5s超时。
try try
{ {
@ -373,23 +372,23 @@ catch (OperationCanceledException ex)
} }
``` ```
> `CancellationTokenSouce.CancelAfter`是一个原生的api。但是在 Unity 中你不应该使用它,因为它依赖于线程计时器。`CancelAfterSlim`是 UniTask 的扩展方法,它使用 PlayerLoop 代替。 > `CancellationTokenSouce.CancelAfter`是一个原生的 api。但是在 Unity 中您不应该使用它,因为它依赖于线程计时器。`CancelAfterSlim`是 UniTask 的扩展方法,它使用 PlayerLoop 代替了线程计时器
如果您想将超时与其他cancellation一起使用请使用`CancellationTokenSource.CreateLinkedTokenSource`. 如果您想将超时与其他 cancellation 一起使用,请使用`CancellationTokenSource.CreateLinkedTokenSource`
```csharp ```csharp
var cancelToken = new CancellationTokenSource(); var cancelToken = new CancellationTokenSource();
cancelButton.onClick.AddListener(()=> cancelButton.onClick.AddListener(()=>
{ {
cancelToken.Cancel(); // 点击按钮后取消 cancelToken.Cancel(); // 点击按钮后取消
}); });
var timeoutToken = new CancellationTokenSource(); var timeoutToken = new CancellationTokenSource();
timeoutToken.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 设置5s超时 timeoutToken.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 设置5s超时
try try
{ {
// 链接token // 链接 token
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancelToken.Token, timeoutToken.Token); var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancelToken.Token, timeoutToken.Token);
await UnityWebRequest.Get("http://foo").SendWebRequest().WithCancellation(linkedTokenSource.Token); await UnityWebRequest.Get("http://foo").SendWebRequest().WithCancellation(linkedTokenSource.Token);
@ -407,19 +406,19 @@ catch (OperationCanceledException ex)
} }
``` ```
优化减少每个调用异步方法超时的 CancellationTokenSource 分配,您可以使用 UniTask 的`TimeoutController`. 减少每次调用异步方法时用于超时的 CancellationTokenSource 的堆内存分配,您可以使用 UniTask 的`TimeoutController`进行优化。
```csharp ```csharp
TimeoutController timeoutController = new TimeoutController(); // 复用timeoutController TimeoutController timeoutController = new TimeoutController(); // 提前创建好,以便复用。
async UniTask FooAsync() async UniTask FooAsync()
{ {
try try
{ {
// 你可以通过 timeoutController.Timeout(TimeSpan) 传递到 cancellationToken. // 您可以通过 timeoutController.Timeout(TimeSpan) 把超时设置传递到 cancellationToken。
await UnityWebRequest.Get("http://foo").SendWebRequest() await UnityWebRequest.Get("http://foo").SendWebRequest()
.WithCancellation(timeoutController.Timeout(TimeSpan.FromSeconds(5))); .WithCancellation(timeoutController.Timeout(TimeSpan.FromSeconds(5)));
timeoutController.Reset(); // 当await完成后调用Reset停止超时计时器并准备下一次复用 timeoutController.Reset(); // 当 await 完成后调用 Reset停止超时计时器并准备下一次复用
} }
catch (OperationCanceledException ex) catch (OperationCanceledException ex)
{ {
@ -431,7 +430,7 @@ async UniTask FooAsync()
} }
``` ```
如果您想将超时其他取消源一起使用,请使用`new TimeoutController(CancellationToken)`. 如果您想将超时结合其他取消源一起使用,请使用`new TimeoutController(CancellationToken)`.
```csharp ```csharp
TimeoutController timeoutController; TimeoutController timeoutController;
@ -444,11 +443,11 @@ void Start()
} }
``` ```
注意UniTask 有`.Timeout`,`.TimeoutWithoutException`方法,但是,如果可能,不要使用这些,请通过`CancellationToken`。由于`.Timeout`作用在task外部无法停止超时任务。`.Timeout`表示超时时忽略结果。如果您将一个`CancellationToken`传递给该方法,它将从任务内部执行,因此可以停止正在运行的任务。 注意UniTask 有`.Timeout``.TimeoutWithoutException`方法,但如果可以的话,尽量不要使用这些方法,请传递`CancellationToken`。因为`.Timeout`是在任务外部执行,所以无法停止超时任务。`.Timeout`意味着超时后忽略结果。如果您将一个`CancellationToken`传递给该方法,它将从任务内部执行,因此可以停止正在运行的任务。
进度 进度
--- ---
一些Unity的异步操作具有`ToUniTask(IProgress<float> progress = null, ...)`扩展方法。 一些 Unity 的异步操作具有`ToUniTask(IProgress<float> progress = null, ...)`扩展方法。
```csharp ```csharp
var progress = Progress.Create<float>(x => Debug.Log(x)); var progress = Progress.Create<float>(x => Debug.Log(x));
@ -458,9 +457,9 @@ var request = await UnityWebRequest.Get("http://google.co.jp")
.ToUniTask(progress: progress); .ToUniTask(progress: progress);
``` ```
您不应该使用原生的`new System.Progress<T>`,因为它每次都会导致GC分配。改为使用`Cysharp.Threading.Tasks.Progress`。这个 progress factory 有两个方法,`Create`和`CreateOnlyValueChanged`。`CreateOnlyValueChanged`仅在进度值更新时调用。 您不应该使用原生的`new System.Progress<T>`,因为每次调用它都会产生堆内存分配。请改用`Cysharp.Threading.Tasks.Progress`。这个 progress 工厂类有两个方法,`Create`和`CreateOnlyValueChanged`。`CreateOnlyValueChanged`仅在进度值更新时调用。
为调用者实现 IProgress 接口会更好,因为这样可以没有 lambda 分配。 为调用者实现 IProgress 接口会更好,这样不会因使用 lambda 而产生堆内存分配。
```csharp ```csharp
public class Foo : MonoBehaviour, IProgress<float> public class Foo : MonoBehaviour, IProgress<float>
@ -481,7 +480,7 @@ public class Foo : MonoBehaviour, IProgress<float>
PlayerLoop PlayerLoop
--- ---
UniTask 在自定义[PlayerLoop](https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoop.html)上运行。UniTask 的基于 playerloop 的方法(例如`Delay`、`DelayFrame`、`asyncOperation.ToUniTask`等)接受这个`PlayerLoopTiming`。 UniTask 运行在自定义[PlayerLoop](https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoop.html)中。UniTask 中基于 PlayerLoop 的方法(如`Delay`、`DelayFrame`、`asyncOperation.ToUniTask`等)接受这个`PlayerLoopTiming`。
```csharp ```csharp
public enum PlayerLoopTiming public enum PlayerLoopTiming
@ -514,35 +513,35 @@ public enum PlayerLoopTiming
} }
``` ```
它表示何时运行,您可以检查[PlayerLoopList.md](https://gist.github.com/neuecc/bc3a1cfd4d74501ad057e49efcd7bdae)到 Unity 的默认 playerloop 并注入 UniTask 的自定义循环。 它表明了异步任务会在哪个时机运行,您可以查阅[PlayerLoopList.md](https://gist.github.com/neuecc/bc3a1cfd4d74501ad057e49efcd7bdae)以了解 Unity 的默认 PlayerLoop 以及注入的 UniTask 的自定义循环。
`PlayerLoopTiming.Update`与协程中的`yield return null`类似,但在 Update(Update 和 uGUI 事件(button.onClick, etc...) 前被调用(在`ScriptRunBehaviourUpdate`时被调用yield return null 在`ScriptRunDelayedDynamicFrameRate`时被调用。`PlayerLoopTiming.FixedUpdate`类似于`WaitForFixedUpdate`。 `PlayerLoopTiming.Update`与协程中的`yield return null`类似,但它会在`ScriptRunBehaviourUpdate`时UpdateUpdate 和 uGUI 事件(button.onClick等之前被调用而 yield return null 是在`ScriptRunDelayedDynamicFrameRate`时被调用。`PlayerLoopTiming.FixedUpdate`类似于`WaitForFixedUpdate`。
> `PlayerLoopTiming.LastPostLateUpdate`不等同于协程的`yield return new WaitForEndOfFrame()`。协程的 WaitForEndOfFrame 似乎在 PlayerLoop 完成后运行。一些需要协程结束帧(`Texture2D.ReadPixels`, `ScreenCapture.CaptureScreenshotAsTexture`, `CommandBuffer`, 等) 的方法在 async/await 时无法正常工作。在这些情况下,请将 MonoBehaviour(coroutine runner) 传递给`UniTask.WaitForEndOfFrame`。例如,`await UniTask.WaitForEndOfFrame(this);`是`yield return new WaitForEndOfFrame()`轻量级0GC的替代方案。 > `PlayerLoopTiming.LastPostLateUpdate`不等同于协程的`yield return new WaitForEndOfFrame()`。协程的 WaitForEndOfFrame 似乎在 PlayerLoop 完成后运行。一些需要协程结束帧的方法(`Texture2D.ReadPixels``ScreenCapture.CaptureScreenshotAsTexture``CommandBuffer`等)在 async/await 时无法正常工作。在这些情况下,请将 MonoBehaviour用于运行协程传递给`UniTask.WaitForEndOfFrame`。例如,`await UniTask.WaitForEndOfFrame(this);`是`yield return new WaitForEndOfFrame()`轻量级无堆内存分配的替代方案。
> 注意在Unity 2023.1或更高版本中,`await UniTask.WaitForEndOfFrame();`不再需要MonoBehaviour。它使用`UnityEngine.Awaitable.EndOfFrameAsync`。 > 注意:在 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`一致. `yield return null`和`UniTask.Yield`相似但不同。`yield return null`总是返回下一帧但`UniTask.Yield`返回下一调用。也就是说,`UniTask.Yield(PlayerLoopTiming.Update)`在 `PreUpdate`上调用,它返回同一帧。`UniTask.NextFrame()`保证返回下一帧,您可以认为它的行为与`yield return null`一致
> UniTask.Yield(without CancellationToken) 是一种特殊类型,返回`YieldAwaitable`并在 YieldRunner 上运行。它是最轻量和最快的。 > UniTask.Yield(不带 CancellationToken是一种特殊类型,返回`YieldAwaitable`并在 YieldRunner 上运行。它是最轻量和最快的。
`AsyncOperation`在原生生命周期返回。例如await `SceneManager.LoadSceneAsync`在`EarlyUpdate.UpdatePreloading`时返回,在此之后,加载的场景的`Start`方法调用自`EarlyUpdate.ScriptRunDelayedStartupFrame`。同样的,`await UnityWebRequest`在`EarlyUpdate.ExecuteMainThreadJobs`时返回. `AsyncOperation`在原生生命周期返回。例如await `SceneManager.LoadSceneAsync`在`EarlyUpdate.UpdatePreloading`时返回,在此之后,在`EarlyUpdate.ScriptRunDelayedStartupFrame`时调用已加载场景的`Start`方法。同样的,`await UnityWebRequest`在`EarlyUpdate.ExecuteMainThreadJobs`时返回。
在 UniTask 中,await 直接使用原生生命周期,`WithCancellation`和`ToUniTask`可以指定使用的原生生命周期。这通常不会有问题,但是`LoadSceneAsync`在等待之后,它会导致开始和继续的不同顺序。所以建议不要使用`LoadSceneAsync.ToUniTask`。 在 UniTask 中,直接 await 使用的是原生生命周期,而`WithCancellation`和`ToUniTask`使用的特定的生命周期。这通常不会有问题,但对于`LoadSceneAsync`,它会导致`Start`方法与 await 之后的逻辑的执行顺序错乱。所以建议不要使用`LoadSceneAsync.ToUniTask`。
> 注意:在Unity 2023.1或更高版本中,当你使用新的`UnityEngine.Awaitable`方法(例如`SceneManager.LoadSceneAsync`确保在你的文件的using语句中包含 `using UnityEngine;` > 注意:在 Unity 2023.1或更高的版本中,当您使用新的`UnityEngine.Awaitable`方法(如`SceneManager.LoadSceneAsync`)时,请确保您的文件的 using 指令区域中包含`using UnityEngine;`
> 这可以通过避免使用`UnityEngine.AsyncOperation`版本来防止编译错误。 > 这可以通过避免使用`UnityEngine.AsyncOperation`版本来防止编译错误。
在堆栈跟踪中,您可以检查它在 playerloop 中的运行位置。 在堆栈跟踪中,您可以检查它在 PlayerLoop 中的运行位置。
![image](https://user-images.githubusercontent.com/46207/83735571-83caea80-a68b-11ea-8d22-5e22864f0d24.png) ![image](https://user-images.githubusercontent.com/46207/83735571-83caea80-a68b-11ea-8d22-5e22864f0d24.png)
默认情况下UniTask 的 PlayerLoop 初始化在`[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]`. 默认情况下UniTask 的 PlayerLoop 在`[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]`初始化。
在 BeforeSceneLoad 中调用方法的顺序是不确定的,所以如果你想在其他 BeforeSceneLoad 方法中使用 UniTask你应该尝试在此之前初始化它 在 BeforeSceneLoad 中调用的方法,它们的执行顺序是不确定的,所以如果您想在其他 BeforeSceneLoad 方法中使用 UniTask您应该尝试在此之前初始化好 PlayerLoop
```csharp ```csharp
// AfterAssembliesLoaded 表示将会在 BeforeSceneLoad之前调用 // AfterAssembliesLoaded 表示将会在 BeforeSceneLoad 之前调用
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
public static void InitUniTaskLoop() public static void InitUniTaskLoop()
{ {
@ -551,19 +550,19 @@ public static void InitUniTaskLoop()
} }
``` ```
如果您导入 Unity 的`Entities`包则会将自定义playerloop重置为默认值`BeforeSceneLoad`并注入 ECS 的循环。当 Unity 在 UniTask 的 initialize 方法之后调用 ECS 的 inject 方法时UniTask 将不再工作 如果您导入了 Unity 的`Entities`包,则会在`BeforeSceneLoad`将自定义 PlayerLoop 重置为默认值,并注入 ECS 的循环。当 Unity 在 UniTask 的初始化方法执行之后调用了 ECS 的注入方法UniTask 将不再起作用
为了解决这个问题,您可以在 ECS 初始化后重新初始化 UniTask PlayerLoop。 为了解决这个问题,您可以在 ECS 初始化后重新初始化 UniTask PlayerLoop。
```csharp ```csharp
// 获取ECS Loop. // 获取 ECS Loop。
var playerLoop = ScriptBehaviourUpdateOrder.CurrentPlayerLoop; var playerLoop = ScriptBehaviourUpdateOrder.CurrentPlayerLoop;
// 设置UniTask PlayerLoop // 设置 UniTask PlayerLoop
PlayerLoopHelper.Initialize(ref playerLoop); PlayerLoopHelper.Initialize(ref playerLoop);
``` ```
您可以通过调用`PlayerLoopHelper.IsInjectedUniTaskPlayerLoop()`来诊断 UniTask 的PlayerLoop是否准备就绪。并且`PlayerLoopHelper.DumpCurrentPlayerLoop`还会将所有当前PlayerLoop记录到控制台。 您可以通过调用`PlayerLoopHelper.IsInjectedUniTaskPlayerLoop()`来诊断 UniTask 的 PlayerLoop 是否准备就绪。并且`PlayerLoopHelper.DumpCurrentPlayerLoop`还会将所有当前 PlayerLoop 记录到控制台。
```csharp ```csharp
void Start() void Start()
@ -573,16 +572,16 @@ void Start()
} }
``` ```
您可以通过除未使用的 PlayerLoopTiming 注入来稍微优化循环成本。您可以在初始化时调用`PlayerLoopHelper.Initialize(InjectPlayerLoopTimings)`。 您可以通过除未使用的 PlayerLoopTiming 注入来稍微优化循环成本。您可以在初始化时调用`PlayerLoopHelper.Initialize(InjectPlayerLoopTimings)`。
```csharp ```csharp
var loop = PlayerLoop.GetCurrentPlayerLoop(); var loop = PlayerLoop.GetCurrentPlayerLoop();
PlayerLoopHelper.Initialize(ref loop, InjectPlayerLoopTimings.Minimum); // 最小化 is Update | FixedUpdate | LastPostLateUpdate PlayerLoopHelper.Initialize(ref loop, InjectPlayerLoopTimings.Minimum); // Minimum 就是 Update | FixedUpdate | LastPostLateUpdate
``` ```
`InjectPlayerLoopTimings`有三个预设,`All``Standard`(除 LastPostLateUpdate 外),`Minimum``Update | FixedUpdate | LastPostLateUpdate`)。默认为全部,您可以组合自定义注入时间,例如`InjectPlayerLoopTimings.Update | InjectPlayerLoopTimings.FixedUpdate | InjectPlayerLoopTimings.PreLateUpdate`. `InjectPlayerLoopTimings`有三个预设,`All``Standard`All 除 LastPostLateUpdate 外),`Minimum``Update | FixedUpdate | LastPostLateUpdate`)。默认为 All您可以通过组合来自定义要注入的时机,例如`InjectPlayerLoopTimings.Update | InjectPlayerLoopTimings.FixedUpdate | InjectPlayerLoopTimings.PreLateUpdate`
使用未注入`PlayerLoopTiming`的[Microsoft.CodeAnalysis.BannedApiAnalyzers](https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md)可能会出错。例如,您可以为`InjectPlayerLoopTimings.Minimum`设置`BannedSymbols.txt` 使用未注入`PlayerLoopTiming`的[Microsoft.CodeAnalysis.BannedApiAnalyzers](https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md)可能会出错。例如,您可以像下列方式那样,为`InjectPlayerLoopTimings.Minimum`设置`BannedSymbols.txt`
```txt ```txt
F:Cysharp.Threading.Tasks.PlayerLoopTiming.Initialization; Isn't injected this PlayerLoop in this project. F:Cysharp.Threading.Tasks.PlayerLoopTiming.Initialization; Isn't injected this PlayerLoop in this project.
@ -600,13 +599,13 @@ F:Cysharp.Threading.Tasks.PlayerLoopTiming.TimeUpdate; Isn't injected this Playe
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastTimeUpdate; Isn't injected this PlayerLoop in this project. F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastTimeUpdate; Isn't injected this PlayerLoop in this project.
``` ```
您可以将`RS0030`严重性配置为错误。 您可以将`RS0030`严重性配置为错误。
![image](https://user-images.githubusercontent.com/46207/109150837-bb933880-77ac-11eb-85ba-4fd15819dbd0.png) ![image](https://user-images.githubusercontent.com/46207/109150837-bb933880-77ac-11eb-85ba-4fd15819dbd0.png)
async void 与 async UniTaskVoid 对比 async void 与 async UniTaskVoid 对比
--- ---
`async void`是一个原生的 C# 任务系统,因此它不在 UniTask 系统上运行。也最好不要使用它。`async UniTaskVoid`是`async UniTask`的轻量级版本,因为它没有等待完成并立即向`UniTaskScheduler.UnobservedTaskException`报告错误。如果您不需要等待(即发即弃),那么使用`UniTaskVoid`会更好。不幸的是,要解除警告,您需要在尾部添加`Forget()`. `async void`是一个原生的 C# 任务系统,因此它不在 UniTask 系统上运行。也最好不要使用它。`async UniTaskVoid`是`async UniTask`的轻量级版本,因为它没有等待完成并立即向`UniTaskScheduler.UnobservedTaskException`报告错误。如果您不需要等待(即发即弃),那么使用`UniTaskVoid`会更好。不幸的是,要解除警告,您需要在尾部添加`Forget()`
```csharp ```csharp
public async UniTaskVoid FireAndForgetMethod() public async UniTaskVoid FireAndForgetMethod()
@ -621,7 +620,7 @@ public void Caller()
} }
``` ```
UniTask 也有`Forget`方法,类似`UniTaskVoid`且效果相同。但是如果你完全不需要使用`await``UniTaskVoid`会更高效。 UniTask 也有`Forget`方法,与`UniTaskVoid`类似且效果相同。如果您完全不需要使用`await`,那么使用`UniTaskVoid`会更高效。
```csharp ```csharp
public async UniTask DoAsync() public async UniTask DoAsync()
@ -636,17 +635,17 @@ public void Caller()
} }
``` ```
要使用注册到事件的异步 lambda请不要使用`async void`。相反,您可以使用`UniTask.Action` 或 `UniTask.UnityAction`,两者都通过`async UniTaskVoid` lambda 创建委托。 要使用注册到事件的异步 lambda请不要使用`async void`。您可以使用`UniTask.Action` 或 `UniTask.UnityAction`来代替两者都通过`async UniTaskVoid` lambda 创建委托。
```csharp ```csharp
Action actEvent; Action actEvent;
UnityAction unityEvent; // UGUI特供 UnityAction unityEvent; // UGUI 特供
// 这样是不好的: async void // 这样是不好的: async void
actEvent += async () => { }; actEvent += async () => { };
unityEvent += async () => { }; unityEvent += async () => { };
// 这样是可以的: 通过lamada创建Action // 这样是可以的: 通过 lamada 创建 Action
actEvent += UniTask.Action(async () => { await UniTask.Yield(); }); actEvent += UniTask.Action(async () => { await UniTask.Yield(); });
unityEvent += UniTask.UnityAction(async () => { await UniTask.Yield(); }); unityEvent += UniTask.UnityAction(async () => { await UniTask.Yield(); });
``` ```
@ -658,7 +657,7 @@ class Sample : MonoBehaviour
{ {
async UniTaskVoid Start() async UniTaskVoid Start()
{ {
// async init code. // 异步初始化代码。
} }
} }
``` ```
@ -675,23 +674,23 @@ UniTaskTracker
- Enable Tracking(Toggle) - 开始跟踪异步/等待 UniTask。性能影响低。 - Enable Tracking(Toggle) - 开始跟踪异步/等待 UniTask。性能影响低。
- Enable StackTrace(Toggle) - 在任务启动时捕获 StackTrace。性能影响高。 - Enable StackTrace(Toggle) - 在任务启动时捕获 StackTrace。性能影响高。
UniTaskTracker 仅用于调试用途,因为启用跟踪和捕获堆栈跟踪很有用,但会对性能产生重大影响。推荐的用法是启用跟踪和堆栈跟踪以查找任务泄漏并在完成时禁用它们。 UniTaskTracker 仅用于调试用途,因为启用跟踪和捕获堆栈跟踪很有用,但会对性能产生重大影响。推荐的用法是只在查找任务泄漏时启用跟踪和堆栈跟踪,并在使用完毕后禁用它们。
外部拓展 外部拓展
--- ---
默认情况下UniTask 支持 TextMeshPro`BindTo(TMP_Text)`和`TMP_InputField`并且TMP_InputField有同原生uGUI `InputField`类似的事件扩展、DOTween`Tween`作为等待和Addressables`AsyncOperationHandle``AsyncOperationHandle<T>`作为等待)。 默认情况下UniTask 支持 TextMeshPro`BindTo(TMP_Text)`和像原生 uGUI `InputField` 那样的事件扩展,如`TMP_InputField`、DOTween`Tween`作为可等待的)和 Addressables`AsyncOperationHandle`和`AsyncOperationHandle<T>`作为等待)。
在单独的 asmdef 中定义,如`UniTask.TextMeshPro``UniTask.DOTween``UniTask.Addressables`。 它们被定义在了如`UniTask.TextMeshPro``UniTask.DOTween``UniTask.Addressables`等单独的 asmdef文件中
Package manager 中导入软件包时,会自动启用对 TextMeshPro 和 Addressables 的支持。 包管理器中导入软件包时,会自动启用对 TextMeshPro 和 Addressables 的支持。
但对于 DOTween 支持,则需要从 [DOTWeen assets](https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676r) 中导入并定义脚本定义符号 `UNITASK_DOTWEEN_SUPPORT` 后才能启用。 但对于 DOTween 支持,则需要从[DOTWeen assets](https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676r)中导入并定义脚本定义符号`UNITASK_DOTWEEN_SUPPORT`后才能启用。
```csharp ```csharp
// 动画序列 // 动画序列
await transform.DOMoveX(2, 10); await transform.DOMoveX(2, 10);
await transform.DOMoveZ(5, 20); await transform.DOMoveZ(5, 20);
// 并行并传递cancellation用于取消 // 并行,并传递 cancellation 用于取消
var ct = this.GetCancellationTokenOnDestroy(); var ct = this.GetCancellationTokenOnDestroy();
await UniTask.WhenAll( await UniTask.WhenAll(
@ -699,7 +698,7 @@ await UniTask.WhenAll(
transform.DOScale(10, 3).WithCancellation(ct)); transform.DOScale(10, 3).WithCancellation(ct));
``` ```
DOTween 支持的默认行为( `await``WithCancellation``ToUniTask`) await tween 被终止。它适用于 Complete(true/false) 和 Kill(true/false)。但是如果你想重用tweens ( `SetAutoKill(false)`)它就不能按预期工作。如果您想等待另一个时间点Tween 中存在以下扩展方法,`AwaitForComplete``AwaitForPause``AwaitForPlay``AwaitForRewind``AwaitForStepComplete`。 DOTween 支持的默认行为`await``WithCancellation``ToUniTask` 会等待到 tween 被终止。它适用于 Complete(true/false) 和 Kill(true/false)。但是如果您想复用 tweens`SetAutoKill(false)`它就不能按预期工作。如果您想等待另一个时间点Tween 中存在以下扩展方法,`AwaitForComplete``AwaitForPause``AwaitForPlay``AwaitForRewind``AwaitForStepComplete`。
AsyncEnumerable 和 Async LINQ AsyncEnumerable 和 Async LINQ
--- ---
@ -713,7 +712,7 @@ await foreach (var _ in UniTaskAsyncEnumerable.EveryUpdate().WithCancellation(to
} }
``` ```
在 C# 7.3 环境中,您可以使用`ForEachAsync`方法以几乎相同的方式工作。 在 C# 7.3 环境中,您可以使用`ForEachAsync`方法以几乎相同的方式工作。
```csharp ```csharp
// C# 7.3(Unity 2018.3~) // C# 7.3(Unity 2018.3~)
@ -723,20 +722,20 @@ await UniTaskAsyncEnumerable.EveryUpdate().ForEachAsync(_ =>
}, token); }, token);
``` ```
`UniTask.WhenEach`类似于.NET 9的`Task.WhenEach`,它可以使用新的方式来等待多个任务。 `UniTask.WhenEach`类似于 .NET 9 的`Task.WhenEach`,它可以使用新的方式来等待多个任务。
```csharp ```csharp
await foreach (var result in UniTask.WhenEach(task1, task2, task3)) await foreach (var result in UniTask.WhenEach(task1, task2, task3))
{ {
// The result is of type WhenEachResult<T>. // 结果的类型为 WhenEachResult<T>
// It contains either `T Result` or `Exception Exception`. // 它包含 `T Result` or `Exception Exception`
// You can check `IsCompletedSuccessfully` or `IsFaulted` to determine whether to access `.Result` or `.Exception`. // 您可以检查 `IsCompletedSuccessfully``IsFaulted` 以确定是访 `.Result` 还是 `.Exception`
// If you want to throw an exception when `IsFaulted` and retrieve the result when successful, use `GetResult()`. // 如果希望在 `IsFaulted` 时抛出异常并在成功时获取结果,可以使用 `GetResult()`
Debug.Log(result.GetResult()); Debug.Log(result.GetResult());
} }
``` ```
UniTaskAsyncEnumerable 实现异步 LINQ类似于 LINQ 的`IEnumerable<T>`或 Rx 的 `IObservable<T>`。所有标准 LINQ 查询运算符都可以应用于异步流。例如,以下代码表示如何将 Where 过滤器应用于每两次单击运行一次的按钮单击异步流。 UniTaskAsyncEnumerable 实现异步 LINQ类似于 LINQ 的`IEnumerable<T>`或 Rx 的 `IObservable<T>`。所有标准 LINQ 查询运算符都可以应用于异步流。例如,以下代码展示了如何将 Where 过滤器应用于每两次单击运行一次的按钮点击异步流。
```csharp ```csharp
await okButton.OnClickAsAsyncEnumerable().Where((x, i) => i % 2 == 0).ForEachAsync(_ => await okButton.OnClickAsAsyncEnumerable().Where((x, i) => i % 2 == 0).ForEachAsync(_ =>
@ -744,7 +743,7 @@ await okButton.OnClickAsAsyncEnumerable().Where((x, i) => i % 2 == 0).ForEachAsy
}); });
``` ```
Fire and Forget 风格(例如,事件处理),你也可以使用`Subscribe`. 即发即弃Fire and Forget风格例如事件处理您也可以使用`Subscribe`。
```csharp ```csharp
okButton.OnClickAsAsyncEnumerable().Where((x, i) => i % 2 == 0).Subscribe(_ => okButton.OnClickAsAsyncEnumerable().Where((x, i) => i % 2 == 0).Subscribe(_ =>
@ -752,13 +751,13 @@ okButton.OnClickAsAsyncEnumerable().Where((x, i) => i % 2 == 0).Subscribe(_ =>
}); });
``` ```
Async LINQ 在 时启用`using Cysharp.Threading.Tasks.Linq;`,并且`UniTaskAsyncEnumerable`在`UniTask.Linq`asmdef 中定义。 在引入`using Cysharp.Threading.Tasks.Linq;`后,异步 LINQ 将被启用,并且`UniTaskAsyncEnumerable`在 asmdef 文件`UniTask.Linq`中定义。
它更接近 UniRxReactive Extensions但 UniTaskAsyncEnumerable 是pull-base的异步流而 Rx 是基于push-base异步流。请注意尽管相似但特征不同并且细节的行为也随之不同。 它更接近 UniRxReactive Extensions但 UniTaskAsyncEnumerable 是基于 pull 的异步流,而 Rx 是基于 push 的异步流。请注意,尽管它们相似,但特性不同,细节也有所不同。
`UniTaskAsyncEnumerable`是类似的入口点`Enumerable`。除了标准查询运算符之外,还有其他 Unity 生成器,例如`EveryUpdate`、`Timer`、`TimerFrame`、`Interval`、`IntervalFrame`和`EveryValueChanged`。并且还添加了额外的 UniTask 原始查询运算符,如`Append``Prepend``DistinctUntilChanged``ToHashSet``Buffer``CombineLatest``Do``Never``ForEachAsync``Pairwise``Publish``Queue``Return``SkipUntil``TakeUntil``SkipUntilCanceled``TakeUntilCanceled``TakeLast``Subscribe`。 `UniTaskAsyncEnumerable`是类似`Enumerable`的入口点。除了标准查询操作符之外,还为 Unity 提供了其他生成器,例如`EveryUpdate`、`Timer`、`TimerFrame`、`Interval`、`IntervalFrame`和`EveryValueChanged`。此外,还添加了 UniTask 原生的查询操作符,如`Append``Prepend``DistinctUntilChanged``ToHashSet``Buffer``CombineLatest``Do``Never``ForEachAsync``Pairwise``Publish``Queue``Return``SkipUntil``TakeUntil``SkipUntilCanceled``TakeUntilCanceled``TakeLast``Subscribe`。
以 Func 作为参数的方法具有三个额外的重载,`***Await``***AwaitWithCancellation`。 以 Func 作为参数的方法具有三个额外的重载,另外两个是`***Await`和`***AwaitWithCancellation`。
```csharp ```csharp
Select(Func<T, TR> selector) Select(Func<T, TR> selector)
@ -766,12 +765,12 @@ SelectAwait(Func<T, UniTask<TR>> selector)
SelectAwaitWithCancellation(Func<T, CancellationToken, UniTask<TR>> selector) SelectAwaitWithCancellation(Func<T, CancellationToken, UniTask<TR>> selector)
``` ```
如果在 func 方法内部使用`async`,请使用***Awaitor `***AwaitWithCancellation` 如果在 func 内部使用`async`方法,请使用`***Await`或`***AwaitWithCancellation`
如何创建异步迭代器C# 8.0 支持异步迭代器(`async yield return`),但它只允许`IAsyncEnumerable<T>`并且当然需要 C# 8.0。UniTask 支持`UniTaskAsyncEnumerable.Create`创建自定义异步迭代器的方法 如何创建异步迭代器C# 8.0 支持异步迭代器(`async yield return`),但它只允许`IAsyncEnumerable<T>`,当然也需要 C# 8.0。UniTask 支持使用`UniTaskAsyncEnumerable.Create`方法来创建自定义异步迭代器
```csharp ```csharp
// IAsyncEnumerableC# 8.0 异步迭代器。( 不要这样用因为IAsyncEnumerable不被UniTask控制). // IAsyncEnumerableC# 8.0 异步迭代器。(请不要这样使用,因为 IAsyncEnumerable 不被 UniTask 所控制)。
public async IAsyncEnumerable<int> MyEveryUpdate([EnumeratorCancellation]CancellationToken cancelationToken = default) public async IAsyncEnumerable<int> MyEveryUpdate([EnumeratorCancellation]CancellationToken cancelationToken = default)
{ {
var frameCount = 0; var frameCount = 0;
@ -786,14 +785,14 @@ public async IAsyncEnumerable<int> MyEveryUpdate([EnumeratorCancellation]Cancell
// UniTaskAsyncEnumerable.Create 并用 `await writer.YieldAsync` 代替 `yield return`. // UniTaskAsyncEnumerable.Create 并用 `await writer.YieldAsync` 代替 `yield return`.
public IUniTaskAsyncEnumerable<int> MyEveryUpdate() public IUniTaskAsyncEnumerable<int> MyEveryUpdate()
{ {
// writer(IAsyncWriter<T>) has `YieldAsync(value)` method. // writer(IAsyncWriter<T>) `YieldAsync(value)` 方法。
return UniTaskAsyncEnumerable.Create<int>(async (writer, token) => return UniTaskAsyncEnumerable.Create<int>(async (writer, token) =>
{ {
var frameCount = 0; var frameCount = 0;
await UniTask.Yield(); await UniTask.Yield();
while (!token.IsCancellationRequested) while (!token.IsCancellationRequested)
{ {
await writer.YieldAsync(frameCount++); // instead of `yield return` await writer.YieldAsync(frameCount++); // 代替 `yield return`
await UniTask.Yield(); await UniTask.Yield();
} }
}); });
@ -802,7 +801,7 @@ public IUniTaskAsyncEnumerable<int> MyEveryUpdate()
可等待事件 可等待事件
--- ---
所有 uGUI 组件都实现了`***AsAsyncEnumerable`异步事件流的转换。 所有 uGUI 组件都实现了`***AsAsyncEnumerable`,以实现对事件的异步流的转换。
```csharp ```csharp
async UniTask TripleClick() async UniTask TripleClick()
@ -826,14 +825,14 @@ async UniTask TripleClick()
} }
} }
// 使用异步LINQ // 使用异步 LINQ
async UniTask TripleClick(CancellationToken token) async UniTask TripleClick(CancellationToken token)
{ {
await button.OnClickAsAsyncEnumerable().Take(3).Last(); await button.OnClickAsAsyncEnumerable().Take(3).Last();
Debug.Log("Three times clicked"); Debug.Log("Three times clicked");
} }
// 使用异步LINQ // 使用异步 LINQ
async UniTask TripleClick(CancellationToken token) async UniTask TripleClick(CancellationToken token)
{ {
await button.OnClickAsAsyncEnumerable().Take(3).ForEachAsync(_ => await button.OnClickAsAsyncEnumerable().Take(3).ForEachAsync(_ =>
@ -844,7 +843,7 @@ async UniTask TripleClick(CancellationToken token)
} }
``` ```
所有MonoBehaviour消息事件可通过`AsyncTriggers`转换成异步流,`AsyncTriggers`可通过引入using Cysharp.Threading.Tasks.Triggers启用.AsyncTrigger可以使用UniTaskAsyncEnumerable来创建并将它作为UniTaskAsyncEnumerable来触发。 所有 MonoBehaviour 消息事件可通过`AsyncTriggers`转换成异步流,`AsyncTriggers`可通过引入`using Cysharp.Threading.Tasks.Triggers;`来启用。`AsyncTriggers`可以使用`GetAsync***Trigger`来创建,并将它作为 UniTaskAsyncEnumerable 来触发。
```csharp ```csharp
var trigger = this.GetOnCollisionEnterAsyncHandler(); var trigger = this.GetOnCollisionEnterAsyncHandler();
@ -852,18 +851,18 @@ await trigger.OnCollisionEnterAsync();
await trigger.OnCollisionEnterAsync(); await trigger.OnCollisionEnterAsync();
await trigger.OnCollisionEnterAsync(); await trigger.OnCollisionEnterAsync();
// every moves. // 每次移动触发。
await this.GetAsyncMoveTrigger().ForEachAsync(axisEventData => await this.GetAsyncMoveTrigger().ForEachAsync(axisEventData =>
{ {
}); });
``` ```
`AsyncReactiveProperty`,`AsyncReadOnlyReactiveProperty`是 UniTask 的 ReactiveProperty 版本。将异步流值绑定到 Unity 组件Text/Selectable/TMP/Text`BindTo`的`IUniTaskAsyncEnumerable<T>`扩展方法。 `AsyncReactiveProperty``AsyncReadOnlyReactiveProperty`是 UniTask 的 ReactiveProperty 版本。`BindTo`的`IUniTaskAsyncEnumerable<T>`扩展方法,可以把异步流值绑定到 Unity 组件Text/Selectable/TMP/Text
```csharp ```csharp
var rp = new AsyncReactiveProperty<int>(99); var rp = new AsyncReactiveProperty<int>(99);
// AsyncReactiveProperty 本身是 IUniTaskAsyncEnumerable可以通过LINQ进行查询 // AsyncReactiveProperty 本身是 IUniTaskAsyncEnumerable可以通过 LINQ 进行查询
rp.ForEachAsync(x => rp.ForEachAsync(x =>
{ {
Debug.Log(x); Debug.Log(x);
@ -878,7 +877,7 @@ rp.WithoutCurrent().BindTo(this.textComponent);
await rp.WaitAsync(); // 一直等待,直到下一个值被设置 await rp.WaitAsync(); // 一直等待,直到下一个值被设置
// 同样支持ToReadOnlyAsyncReactiveProperty // 同样支持 ToReadOnlyAsyncReactiveProperty
var rp2 = new AsyncReactiveProperty<int>(99); var rp2 = new AsyncReactiveProperty<int>(99);
var rorp = rp.CombineLatest(rp2, (x, y) => (x, y)).ToReadOnlyAsyncReactiveProperty(CancellationToken.None); var rorp = rp.CombineLatest(rp2, (x, y) => (x, y)).ToReadOnlyAsyncReactiveProperty(CancellationToken.None);
``` ```
@ -886,26 +885,26 @@ var rorp = rp.CombineLatest(rp2, (x, y) => (x, y)).ToReadOnlyAsyncReactiveProper
在序列中的异步处理完成之前pull-based异步流不会获取下一个值。这可能会从按钮等推送类型的事件中溢出数据。 在序列中的异步处理完成之前pull-based异步流不会获取下一个值。这可能会从按钮等推送类型的事件中溢出数据。
```csharp ```csharp
// 在3s完成前,无法获取event // 在3s延迟结束前,无法获取 event
await button.OnClickAsAsyncEnumerable().ForEachAwaitAsync(async x => await button.OnClickAsAsyncEnumerable().ForEachAwaitAsync(async x =>
{ {
await UniTask.Delay(TimeSpan.FromSeconds(3)); await UniTask.Delay(TimeSpan.FromSeconds(3));
}); });
``` ```
很有用(防止双击),但有时没用。 (在防止双击方面)是有用的,但有时也并非都有用。
使用`Queue()`方法还将在异步处理期间对事件进行排队。 使用`Queue()`方法在异步处理期间也会对事件进行排队。
```csharp ```csharp
// 异步处理中对message进行排队 // 异步处理中对 message 进行排队
await button.OnClickAsAsyncEnumerable().Queue().ForEachAwaitAsync(async x => await button.OnClickAsAsyncEnumerable().Queue().ForEachAwaitAsync(async x =>
{ {
await UniTask.Delay(TimeSpan.FromSeconds(3)); await UniTask.Delay(TimeSpan.FromSeconds(3));
}); });
``` ```
或使用`Subscribe`fire and forget 风格 或使用即发即弃风格的`Subscribe`。
```csharp ```csharp
button.OnClickAsAsyncEnumerable().Subscribe(async x => button.OnClickAsAsyncEnumerable().Subscribe(async x =>
@ -918,11 +917,11 @@ Channel
--- ---
`Channel`与[System.Threading.Tasks.Channels](https://docs.microsoft.com/en-us/dotnet/api/system.threading.channels?view=netcore-3.1)相同,类似于 GoLang Channel。 `Channel`与[System.Threading.Tasks.Channels](https://docs.microsoft.com/en-us/dotnet/api/system.threading.channels?view=netcore-3.1)相同,类似于 GoLang Channel。
目前只支持多生产者、单消费者无界渠道。它可以由`Channel.CreateSingleConsumerUnbounded<T>()`. 目前只支持多生产者、单消费者无界 Channel。它可以通过`Channel.CreateSingleConsumerUnbounded<T>()`来创建。
对于 producer(`.Writer`),用`TryWrite`推送值和`TryComplete`完成通道。对于 consumer(`.Reader`),使用`TryRead`、`WaitToReadAsync`、`ReadAsync`和`Completion``ReadAllAsync`来读取队列的消息。 对于生产者(`.Writer`),使用`TryWrite`来推送值,使用`TryComplete`来完成 Channel。对于消费者(`.Reader`),使用`TryRead`、`WaitToReadAsync`、`ReadAsync`和`Completion``ReadAllAsync`来读取队列的消息。
`ReadAllAsync`返回`IUniTaskAsyncEnumerable<T>` 查询 LINQ 运算符。Reader 只允许单消费者,但使用`.Publish()`查询运算符来启用多播消息。例如,制作 pub/sub 实用程序 `ReadAllAsync`返回`IUniTaskAsyncEnumerable<T>` 因此可以使用 LINQ 操作符。Reader 只允许单消费者,但可以使用`.Publish()`查询操作符来启用多播消息。例如,可以制作发布/订阅工具
```csharp ```csharp
public class AsyncMessageBroker<T> : IDisposable public class AsyncMessageBroker<T> : IDisposable
@ -959,11 +958,11 @@ public class AsyncMessageBroker<T> : IDisposable
与 Awaitable 对比 与 Awaitable 对比
--- ---
Unity 6 引入了可等待类型[Awaitable](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Awaitable.html)。简而言之Awaitable可以被认为是UniTask的一个子集并且事实上Awaitable的设计也受UniTask的影响。它应该能够处理基于PlayerLoop的await池化Task以及支持以类似的方式使用`CancellationToken`进行取消。随着它被包含在标准库中您可能想知道是继续使用UniTask还是迁移到Awaitable。以下是简要指南。 Unity 6 引入了可等待类型[Awaitable](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Awaitable.html)。简而言之Awaitable 可以被认为是 UniTask 的一个子集并且事实上Awaitable的设计也受 UniTask 的影响。它应该能够处理基于 PlayerLoop await池化 Task以及支持以类似的方式使用`CancellationToken`进行取消。随着它被包含在标准库中,您可能想知道是继续使用 UniTask 还是迁移到 Awaitable。以下是简要指南。
首先Awaitable提供的功能与协程提供的功能相同。使用await代替`yield return``await NextFrameAsync()`代替`yield return null``WaitForSeconds`和`EndOfFrame`等价。然而,这只是两者之间的差异。就功能而言,它是基于协程的,缺乏基于任务的特性。在使用async/await的实际应用程序开发中,像`WhenAll`这样的操作是必不可少的。此外UniTask支持许多基于帧的操作如`DelayFrame`)和更灵活的PlayerLoopTiming控件这些在Awaitable中是不可用的。当然它也没有跟踪窗口。 首先Awaitable 提供的功能与协程提供的功能相同。使用 await 代替`yield return``await NextFrameAsync()`代替`yield return null``WaitForSeconds`和`EndOfFrame`等价。然而,这只是两者之间的差异。就功能而言,它是基于协程的,缺乏基于 Task 的特性。在使用 async/await 的实际应用程序开发中,像`WhenAll`这样的操作是必不可少的。此外UniTask 支持许多基于帧的操作(如`DelayFrame`)和更灵活的 PlayerLoopTiming 控制,这些在 Awaitable 中是不可用的。当然,它也没有跟踪器窗口。
因此,我建议使用UniTask进行应用程序开发。UniTask是Awaitable的超集包含了许多基本特性。对于库开发如果您希望避免外部依赖那么使用Awaitable作为方法的返回类型将是合适的。使用`AsUniTask`可以将Awaitable转换为UniTask因此在UniTask库中处理基于Awaitable的功能没有问题。当然如果你不需要担心依赖关系即使在库开发中使用UniTask也会是最好的选择。 因此,我推荐在应用程序开发中使用 UniTask。UniTask 是 Awaitable 的超集,并包含了许多基本特性。对于库开发,如果您希望避免外部依赖,可以使用 Awaitable 作为方法的返回类型。因为 Awaitable 可以使用`AsUniTask`转换为 UniTask所以支持在 UniTask 库中处理基于 Awaitable 的功能。即便是在库开发中,如果您不需要担心依赖关系,使用 UniTask 也会是您的最佳选择。
单元测试 单元测试
--- ---
@ -990,46 +989,46 @@ public IEnumerator DelayIgnore() => UniTask.ToCoroutine(async () =>
}); });
``` ```
UniTask 自的单元测试是使用 Unity Test Runner 和[Cysharp/RuntimeUnitTestToolkit](https://github.com/Cysharp/RuntimeUnitTestToolkit)编写的,以与 CI 集成并检查 IL2CPP 是否正常工作。 UniTask 自的单元测试是使用 Unity Test Runner 和[Cysharp/RuntimeUnitTestToolkit](https://github.com/Cysharp/RuntimeUnitTestToolkit)编写的,以集成到 CI 中并检查 IL2CPP 是否正常工作。
## 线程池限制 ## 线程池限制
大多数 UniTask 方法在单个线程 (PlayerLoop) 上运行,只有`UniTask.Run``Task.Run`等效)和`UniTask.SwitchToThreadPool`在线程池上运行。如果您使用线程池,它将无法与 WebGL 等平台兼容。 大多数 UniTask 方法在单个线程 (PlayerLoop) 上运行,只有`UniTask.Run`等同于`Task.Run`)和`UniTask.SwitchToThreadPool`在线程池上运行。如果您使用线程池,它将无法与 WebGL 等平台兼容。
`UniTask.Run`现在已弃用。可以改用`UniTask.RunOnThreadPool`。并且还要考虑是否可以使用`UniTask.Create`或`UniTask.Void`。 `UniTask.Run`现在已弃用。可以改用`UniTask.RunOnThreadPool`。并且还要考虑是否可以使用`UniTask.Create`或`UniTask.Void`。
## IEnumerator.ToUniTask 限制 ## IEnumerator.ToUniTask 限制
您可以将协程IEnumerator转换为 UniTask或直接等待),但它有一些限制。 您可以将协程IEnumerator转换为 UniTask或直接 await),但它有一些限制。
- 不支持`WaitForEndOfFrame``WaitForFixedUpdate``Coroutine` - 不支持`WaitForEndOfFrame``WaitForFixedUpdate``Coroutine`
- Loop生命周期与`StartCoroutine`不一样,它使用指定`PlayerLoopTiming`的并且默认情况下,`PlayerLoopTiming.Update`在 MonoBehaviour`Update`和`StartCoroutine`的循环之前运行。 - 生命周期与`StartCoroutine`不一样,它使用指定的`PlayerLoopTiming`,并且默认情况下,`PlayerLoopTiming.Update`在 MonoBehaviour 的`Update`和`StartCoroutine`的循环之前执行。
如果您想要从协程到异步的完全兼容转换,请使用`IEnumerator.ToUniTask(MonoBehaviour coroutineRunner)`重载。它在参数 MonoBehaviour 的实例上执行 StartCoroutine 并等待它在 UniTask 中完成。 如果您想要实现从协程到异步的完全兼容转换,请使用`IEnumerator.ToUniTask(MonoBehaviour coroutineRunner)`重载。它会在传入的 MonoBehaviour 实例中执行 StartCoroutine 并在 UniTask 中等待它完成。
## 关于UnityEditor ## 关于 UnityEditor
UniTask 可以像编辑器协程一样在 Unity 编辑器上运行。但是,有一些限制。 UniTask 可以像编辑器协程一样在 Unity 编辑器上运行。但有一些限制。
- UniTask.Delay 的 DelayType.DeltaTime、UnscaledDeltaTime 无法正常工作,因为它们无法在编辑器中获取 deltaTime。因此在 EditMode 上运行,会自动将 DelayType 更改为`DelayType.Realtime`等待正确的时间 - UniTask.Delay 的 DelayType.DeltaTime、UnscaledDeltaTime 无法正常工作,因为它们无法在编辑器中获取 deltaTime。因此在 EditMode 下运行时,会自动将 DelayType 更改为能等待正确的时间的`DelayType.Realtime`
- 所有 PlayerLoopTiming 都在`EditorApplication.update`生命周期上运行。 - 所有 PlayerLoopTiming 都在`EditorApplication.update`生命周期上运行。
- 带`-quit`的`-batchmode`带不起作用,因为 Unity`EditorApplication.update`在单帧后不会运行并退出。相反,不要使用`-quit`并手动退出`EditorApplication.Exit(0)`. - 带`-quit`的`-batchmode`不起作用,因为 Unity 不会执行 `EditorApplication.update` 并在一帧后退出。因此,不要使用`-quit`并使用`EditorApplication.Exit(0)`手动退出。
与原生Task API对比 与原生 Task API 对比
--- ---
UniTask 有许多原生的 Task-like API。此表显示了一一对应的 API 是什么 UniTask 有许多原生的类Task API。此表展示了两者相对应的 API
使用原生类型。 使用原生类型。
| .NET Type | UniTask Type | | .NET 类型 | UniTask 类型 |
| --- | --- | |---------------------------| --- |
| `IProgress<T>` | --- | | `IProgress<T>` | --- |
| `CancellationToken` | --- | | `CancellationToken` | --- |
| `CancellationTokenSource` | --- | | `CancellationTokenSource` | --- |
使用 UniTask 类型. 使用 UniTask 类型
| .NET Type | UniTask Type | | .NET 类型 | UniTask 类型 |
| --- | --- | | --- | --- |
| `Task`/`ValueTask` | `UniTask` | | `Task`/`ValueTask` | `UniTask` |
| `Task<T>`/`ValueTask<T>` | `UniTask<T>` | | `Task<T>`/`ValueTask<T>` | `UniTask<T>` |
@ -1064,7 +1063,7 @@ UniTask 有许多原生的 Task-like API。此表显示了一一对应的 API
池化配置 池化配置
--- ---
UniTask 积极缓存异步promise对象实现零分配(有关技术细节,请参阅博客文章[UniTask v2 — Unity 的零分配异步/等待,使用异步 LINQ](https://medium.com/@neuecc/unitask-v2-zero-allocation-async-await-for-unity-with-asynchronous-linq-1aa9c96aa7dd))。默认情况下,它缓存所有promise ,但您可以配置`TaskPool.SetMaxPoolSize`为您的值,该值表示每种类型的缓存大小。`TaskPool.GetCacheSizeInfo`返回池中当前缓存的对象。 UniTask 通过积极缓存异步 promise 对象实现零堆内存分配(有关技术细节,请参阅博客文章[UniTask v2 — 适用于 Unity 的零堆内存分配的async/await支持异步 LINQ](https://medium.com/@neuecc/unitask-v2-zero-allocation-async-await-for-unity-with-asynchronous-linq-1aa9c96aa7dd))。默认情况下,它缓存所有 promise但您可以通过调用`TaskPool.SetMaxPoolSize`方法来自定义每种类型的最大缓存大小。`TaskPool.GetCacheSizeInfo`返回池中当前缓存的对象。
```csharp ```csharp
foreach (var (type, size) in TaskPool.GetCacheSizeInfo()) foreach (var (type, size) in TaskPool.GetCacheSizeInfo())
@ -1073,19 +1072,19 @@ foreach (var (type, size) in TaskPool.GetCacheSizeInfo())
} }
``` ```
Profiler下的分配 Profiler 下的堆内存分配
--- ---
在 UnityEditor 中,分析器显示编译器生成的 AsyncStateMachine 的分配但它只发生在调试开发构建中。C# 编译器将 AsyncStateMachine 生成为 Debug 构建的类和 Release 构建的结构。 在 UnityEditor 中,能从 profiler 中看到编译器生成的 AsyncStateMachine 的堆内存分配但它只出现在Debugdevelopment构建中。C# 编译器在Debug 构建时将 AsyncStateMachine 生成为类而在Release 构建时将其生成为结构。
Unity 从 2020.1 开始支持代码优化选项(右,页脚)。 Unity 从2020.1版本开始支持代码优化选项(位于右下角)。
![](https://user-images.githubusercontent.com/46207/89967342-2f944600-dc8c-11ea-99fc-0b74527a16f6.png) ![](https://user-images.githubusercontent.com/46207/89967342-2f944600-dc8c-11ea-99fc-0b74527a16f6.png)
您可以将 C# 编译器优化更改为 release 以删除开发版本中的 AsyncStateMachine 分配。此优化选项也可以通过设置`Compilation.CompilationPipeline-codeOptimization`和`Compilation.CodeOptimization`。 在开发构建中,您可以通过将 C# 编译器优化设置为 release 模式来移除 AsyncStateMachine 的堆内存分配。此优化选项也可以通过`Compilation.CompilationPipeline-codeOptimization`和`Compilation.CodeOptimization`来设置
UniTaskSynchronizationContext UniTaskSynchronizationContext
--- ---
Unity 的默认 SynchronizationContext( `UnitySynchronizationContext`) 在性能方面表现不佳。UniTask 绕过`SynchronizationContext`(和`ExecutionContext`) 因此不使用它,但如果存在`async Task`,则仍然使用它。`UniTaskSynchronizationContext`是`UnitySynchronizationContext`性能更好的替代品。 Unity 的默认 SynchronizationContext(`UnitySynchronizationContext`) 在性能方面表现不佳。UniTask 绕过`SynchronizationContext`(和`ExecutionContext`) 因此 UniTask 不使用它,但如果存在`async Task`,则仍然使用它。`UniTaskSynchronizationContext`是`UnitySynchronizationContext`性能更好的替代品。
```csharp ```csharp
public class SyncContextInjecter public class SyncContextInjecter
@ -1100,38 +1099,38 @@ public class SyncContextInjecter
这是一个可选的选择,并不总是推荐;`UniTaskSynchronizationContext`性能不如`async UniTask`,并且不是完整的 UniTask 替代品。它也不保证与`UnitySynchronizationContext`完全兼容 这是一个可选的选择,并不总是推荐;`UniTaskSynchronizationContext`性能不如`async UniTask`,并且不是完整的 UniTask 替代品。它也不保证与`UnitySynchronizationContext`完全兼容
API References API 文档
--- ---
UniTask 的 API 参考由[DocFX](https://dotnet.github.io/docfx/)和[Cysharp/DocfXTemplate托管在](https://github.com/Cysharp/DocfxTemplate)[cysharp.github.io/UniTask](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.html)上 UniTask 的 API 文档托管在[cysharp.github.io/UniTask](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.html),使用[DocFX](https://dotnet.github.io/docfx/)和[Cysharp/DocfXTemplate](https://github.com/Cysharp/DocfxTemplate)生成
例如UniTask 的工厂方法可以在[UniTask#methods](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.UniTask.html#methods-1)中看到。UniTaskAsyncEnumerable 的工厂/扩展方法可以在[UniTaskAsyncEnumerable#methods](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.Linq.UniTaskAsyncEnumerable.html#methods-1)中看到 例如UniTask 的工厂方法可以在[UniTask#methods](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.UniTask.html#methods-1)中查阅。UniTaskAsyncEnumerable 的工厂方法和扩展方法可以在[UniTaskAsyncEnumerable#methods](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.Linq.UniTaskAsyncEnumerable.html#methods-1)中查阅
UPM Package UPM
--- ---
### 通过 git URL 安装 ### 通过 git URL 安装
需要支持 git 包路径查询参数的 unity 版本Unity >= 2019.3.4f1Unity >= 2020.1a21)。您可以添加`https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask`到包管理器 需要支持 git 包路径查询参数的 Unity 版本Unity >= 2019.3.4f1Unity >= 2020.1a21)。您可以在包管理器中添加`https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask`
![image](https://user-images.githubusercontent.com/46207/79450714-3aadd100-8020-11ea-8aae-b8d87fc4d7be.png) ![image](https://user-images.githubusercontent.com/46207/79450714-3aadd100-8020-11ea-8aae-b8d87fc4d7be.png)
![image](https://user-images.githubusercontent.com/46207/83702872-e0f17c80-a648-11ea-8183-7469dcd4f810.png) ![image](https://user-images.githubusercontent.com/46207/83702872-e0f17c80-a648-11ea-8183-7469dcd4f810.png)
或添加`"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask"`到`Packages/manifest.json`. 在`Packages/manifest.json`中添加`"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask"`
如果要设置目标版本UniTask 使用`*.*.*`发布标签,因此您可以指定一个版本,如`#2.1.0`。例如`https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#2.1.0`. UniTask 使用`*.*.*`发布标签来指定版本,因此如果您要设置指定版本,您可以在后面添加像`#2.1.0`这样的版本标签。例如`https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#2.1.0` 。
.NET Core 关于 .NET Core
--- ---
对于 .NET Core请使用 NuGet。 对于 .NET Core请使用 NuGet。
> PM> Install-Package [UniTask](https://www.nuget.org/packages/UniTask) > PM> Install-Package [UniTask](https://www.nuget.org/packages/UniTask)
.NET Core 版本的 UniTask 是 Unity UniTask 的子集,移除了 PlayerLoop 依赖的方法。 .NET Core 版本的 UniTask 是 Unity 版本的 UniTask 的子集,移除了依赖 PlayerLoop 的方法。
它以比标准 Task/ValueTask 更高的性能运行,但在使用时应注意忽略 ExecutionContext/SynchronizationContext。`AsyncLocal`也不起作用,因为它忽略了 ExecutionContext 相比于原生 Task 和 ValueTask它能以更高的性能运行但在使用时应注意忽略 ExecutionContext 和 SynchronizationContext。因为它忽略了 ExecutionContext`AsyncLocal`也不起作用
如果您在内部使用 UniTask但将 ValueTask 作为外部 API 提供,您可以编写如下(受[PooledAwait](https://github.com/mgravell/PooledAwait)启发)代码 如果您在内部使用 UniTask但将 ValueTask 作为外部 API 提供,您可以编写如下代码(受[PooledAwait](https://github.com/mgravell/PooledAwait)启发)。
```csharp ```csharp
public class ZeroAllocAsyncAwaitInDotNetCore public class ZeroAllocAsyncAwaitInDotNetCore
@ -1162,10 +1161,10 @@ public ValueTask TestAsync()
} }
``` ```
.NET Core 版本允许用户在与Unity共享代码时例如[CysharpOnion](https://github.com/Cysharp/MagicOnion/)像使用接口一样使用UniTask。.NET Core 版本的 UniTask 可以提供丝滑的代码共享体验 .NET Core 版本的 UniTask 是为了让用户在与 Unity 共享代码时(例如使用[CysharpOnion](https://github.com/Cysharp/MagicOnion/)),能够将 UniTask 用作接口。.NET Core 版本的 UniTask 使得代码共享更加顺畅
WhenAll 等实用方法作为 UniTask 的补充,由[Cysharp/ValueTaskSupplement](https://github.com/Cysharp/ValueTaskSupplement)提供。 [Cysharp/ValueTaskSupplement](https://github.com/Cysharp/ValueTaskSupplement)提供了一些实用方法,如 WhenAll这些方法等效于 UniTask
License 许可证
--- ---
仓库基于MIT协议 库采用MIT许可证