paint-brush
Unity 的开发的完善异步编写程序上手 經過@dmitrii
5,701 讀數
5,701 讀數

Unity 开发的完整异步编程入门

历经 Dmitrii Ivashchenko31m2023/03/30
Read on Terminal Reader

太長; 讀書

在本文中,我们将讨论避免任何此类问题。我们将推荐使用异步编程技术在单独的线程中执行这些任务,从而让主线程可以自由执行其他任务。这将有助于确保流畅且反应灵敏的游戏玩法,并(希望)让游戏玩家满意。
featured image - Unity 开发的完整异步编程入门
Dmitrii Ivashchenko HackerNoon profile picture
0-item
网络游戏中发展中的一个任務不只是微信同步的——同旁内角是异步的。这是因为着同旁内角并不会在网络游戏中编号轴线性执行程序。中仅一个异步任務很有可能想要非常的长的用时才华已完成,而于他任務则与聚集来计算想绑定。


一些最常见的游戏异步任务如下:

  • 完成网路标准
  • 初始化环境、网络资源和另外固定资产
  • 读写文件目录
  • 使用在科学决策的人工控制智慧
  • 长动画片队列
  • 工作更多数据库
  • 寻找自己路劲


当今,尤其重要的性的是,是由于那些 Unity 二维码也都在是一个线程中运动,往往其余累似所诉作业的作业若果同时进行执行工作,也会影响途径程被拥塞,然后影响帧遗失。


大众好,我是 Dmitrii Ivashchenko,我是 MY.GAMES 设计规划团队图片的提供人。在本论文中,你们将专题讨论尽量不要其余相应间题。你们将推送用异步源程序技术应用在直接的线程中履行等责任,进而让每日任务程能能公民权履行另外的责任。这将能控制保持帧数且生理反应灵敏性的棋牌玩游戏玩法,并(祝愿)让棋牌小伙伴十分满意。

协程

首先,让我们谈谈协程。它们于 2011 年在 Unity 中引入,甚至在 .NET 中出现 async / await 之前。在 Unity 中,协程允许我们在多个帧上执行一组指令,而不是一次执行所有指令。它们类似于线程,但轻量级并集成到 Unity 的更新循环中,使它们非常适合游戏开发。


(顺带一提,从的三国时期最看,协程是Unity中最开始确定异步进行操作的方案,故网上平台大方面文章内容还是并于协程的。)


要创建协程,您需要声明一个返回类型为IEnumerator函数。此函数可以包含您希望协程执行的任何逻辑。


要启动协程,您需要在MonoBehaviour实例上调用StartCoroutine方法并将协程函数作为参数传递:

 public class Example : MonoBehaviour { void Start() { StartCoroutine(MyCoroutine()); } IEnumerator MyCoroutine() { Debug.Log("Starting coroutine"); yield return null; Debug.Log("Executing coroutine"); yield return null; Debug.Log("Finishing coroutine"); } }


Unity 中有几个可用的 yield 指令,例如WaitForSecondsWaitForEndOfFrameWaitForFixedUpdateWaitForSecondsRealtimeWaitUntil以及其他一些指令。重要的是要记住,使用它们会导致分配,因此应尽可能重用它们。


例如,考虑文档中的这个方法:

 IEnumerator Fade() { Color c = renderer.material.color; for (float alpha = 1f; alpha >= 0; alpha -= 0.1f) { ca = alpha; renderer.material.color = c; yield return new WaitForSeconds(.1f); } }


随着循环的每次迭代,将创建new WaitForSeconds(.1f)的新实例。取而代之的是,我们可以将创建移到循环之外并避免分配:

 IEnumerator Fade() { Color c = renderer.material.color; **var waitForSeconds = new WaitForSeconds(0.2f);** for (float alpha = 1f; alpha >= 0; alpha -= 0.1f) { ca = alpha; renderer.material.color = c; yield return **waitForSeconds**; } }


另一个需要注意的重要属性是yield return可以与 Unity 提供的所有Async方法一起使用,因为AsyncOperationYieldInstruction的后代:

 yield return SceneManager.LoadSceneAsync("path/to/scene.unity");

协程的一些可能的陷阱

总而言之表明,协程都有有些优点和缺点想要要注意:


  • 不可能返回长时间操作的结果。您仍然需要将传递给协程并在完成时调用的回调以从中提取任何数据。
  • 协程与启动它的MonoBehaviour严格相关。如果GameObject被关闭或销毁,协程将停止处理。
  • 由于存在 yield 语法,因此无法使用try-catch-finally结构。
  • yield return之后,在下一个代码开始执行之前,至少会经过一帧。
  • 分配 lambda 和协程本身

承诺

Promises是一种用于组织异步操作并使异步操作更具可读性的模式。由于它们在许多第三方 JavaScript 库中的使用而变得流行,并且自 ES6 以来,它们已被原生实现。


动用 Promises 时,大家会立马从您的异步涵数跳回一家物体。这能够加载者在等待操作的的克服(或内部错误)。


从普遍性上讲,这不使异步方式能否退回值并像关联方式如此“表现”:什么和什么不再是实时退回结果是值,还是“诺言”什么和什么将在未来生活其中一个时长退回其中一个值。


Unity 有哪种 Promises 实行:


与 Promise 交互的主要方式是通过回调函数


您会判定有一个调整指数函数值,当 Promise 被解答时将被启用,和另有一个调整指数函数值将在 Promise 被阻止时被启用。他们调整接收入异步操作的步骤的结杲当做数据,进而可以使用于审理进一歩的操作的步骤。


根据 Promises/A+ 组织这些,Promise 可以处于以下三种状态之一:


  • Pending :初始状态,这意味着异步操作仍在进行中,操作的结果尚不可知。
  • Fulfilled ( Resolved ):已解决的状态伴随着一个代表操作结果的值。
  • Rejected :如果异步操作因任何原因失败,则称 Promise 被“拒绝”。拒绝状态伴随着失败的原因。

更多关于承诺

除此以外,promise 能否链接代码在同时,是这样个 Promise 的后果能否平常确实另个 Promise 的后果。


随后,您能否组建是一个从贴心高防服务器了解点大数据源的 Promise,第二步施用该大数据源组建另是一个 Promise 来制定点计算出和相关操作流程:
 var promise = MakeRequest("//some.api") .Then(response => Parse(response)) .Then(result => OnRequestSuccess(result)) .Then(() => PlaySomeAnimation()) .Catch(exception => OnRequestFailed(exception));


底下是如此阻止进行异步基本操作的措施的范例:
 public IPromise<string> MakeRequest(string url) { // Create a new promise object var promise = new Promise<string>(); // Create a new web client using var client = new WebClient(); // Add a handler for the DownloadStringCompleted event client.DownloadStringCompleted += (sender, eventArgs) => { // If an error occurred, reject the promise if (eventArgs.Error != null) { promise.Reject(eventArgs.Error); } // Otherwise, resolve the promise with the result else { promise.Resolve(eventArgs.Result); } }; // Start the download asynchronously client.DownloadStringAsync(new Uri(url), null); // Return the promise return promise; }


我们还可以将协程包装在Promise中:

 void Start() { // Load the scene and then show the intro animation LoadScene("path/to/scene.unity") .Then(() => ShowIntroAnimation()) .Then( ... ); } // Load a scene and return a promise Promise LoadScene(string sceneName) { // Create a new promise var promise = new Promise(); // Start a coroutine to load the scene StartCoroutine(LoadSceneRoutine(promise, sceneName)); // Return the promise return promise; } IEnumerator LoadSceneRoutine(Promise promise, string sceneName) { // Load the scene asynchronously yield return SceneManager.LoadSceneAsync(sceneName); // Resolve the promise once the scene is loaded promise.Resolve(); }


当然,您可以使用ThenAll / Promise.AllThenRace / Promise.Race组织承诺执行顺序的任意组合:

 // Execute the following two promises in sequence Promise.Sequence( () => Promise.All( // Execute the two promises in parallel RunAnimation("Foo"), PlaySound("Bar") ), () => Promise.Race( // Execute the two promises in a race RunAnimation("One"), PlaySound("Two") ) );

承诺中“没有希望”的部分

其实采用上去很便宜,但 promises 也是有一系列问题:


  • 开销:与使用其他异步编程方法(如协程)相比,创建 Promises 涉及额外的开销。在某些情况下,这会导致性能下降。
  • 调试:调试 Promises 可能比调试其他异步编程模式更困难。跟踪执行流程和识别错误来源可能很困难。
  • 异常处理:与其他异步编程模式相比,Promises 的异常处理可能更复杂。管理 Promise 链中发生的错误和异常可能很困难。

异步/等待任务

自 5.0 (2012) 版来党,async/await 功用一致是 C# 的这部件,另外随着时间推移 .NET 4.x 运作时的实现时 Unity 2017 中建立。


在 .NET 的发展历程中,可以分为以下几个阶段:


  1. EAP (基于事件的异步模式):这种方法基于在操作完成时触发的事件和调用此操作的常规方法。
  2. APM (异步编程模型):这种方法基于两种方法。 BeginSmth方法返回IAsyncResult接口。 EndSmth方法采用IAsyncResult ;如果在调用EndSmth时操作尚未完成,则线程将被阻塞。
  3. TAP (基于任务的异步模式):通过引入 async/await 以及类型TaskTask<TResult>改进了这个概念。


犹豫第三种方式的实现目标,半年前的方式就已经 过去式了。

要创建一个异步方法,该方法必须用关键字async标记,内部包含一个await ,返回值必须是TaskTask<T>void (不推荐)。

 public async Task Example() { SyncMethodA(); await Task.Delay(1000); // the first async operation SyncMethodB(); await Task.Delay(2000); // the second async operation SyncMethodC(); await Task.Delay(3000); // the third async operation }


在此示例中,执行将如下进行:


  1. 首先,将执行调用第一个异步操作 ( SyncMethodA ) 之前的代码。
  2. 第一个异步操作await Task.Delay(1000)已启动并预计将被执行。同时,将保存异步操作完成(“延续”)时要调用的代码。
  3. 在第一个异步操作完成后,“继续”——直到下一个异步操作( SyncMethodB )的代码将开始执行。
  4. 第二个异步操作 ( await Task.Delay(2000) ) 已启动并预计将被执行。同时,continuation — 第二个异步操作 ( SyncMethodC ) 之后的代码将被保留。
  5. 第二次异步操作完成后,会执行SyncMethodC ,接着执行等待第三次异步操作await Task.Delay(3000)


这便是1个简易的说明,根据实际情况上 async/await 是日语语法糖,也可以不方便地都会进行异步做法并等着她们已完成。


您还可以使用WhenAllWhenAny组织执行顺序的任意组合:

 var allTasks = Task.WhenAll( Task.Run(() => { /* ... */ }), Task.Run(() => { /* ... */ }), Task.Run(() => { /* ... */ }) ); allTasks.ContinueWith(t => { Console.WriteLine("All the tasks are completed"); }); var anyTask = Task.WhenAny( Task.Run(() => { /* ... */ }), Task.Run(() => { /* ... */ }), Task.Run(() => { /* ... */ }) ); anyTask.ContinueWith(t => { Console.WriteLine("One of tasks is completed"); });

同步状态机

C#编译器将 async/await 调用转换为IAsyncStateMachine状态机,这是完成异步操作必须执行的一组顺序操作。


只要资源赋值 await 操控时,方式机完整其运行并守候该操控完整,并且依然执行命令命令下其中一个操控。这能能当你不再闭塞任务程的情况发生传到后台管理系统执行命令命令异步操控,还能能使异步的方法资源赋值更比较简单、更易读。


因此, Example方法被转换为使用注解[AsyncStateMachine(typeof(ExampleStateMachine))]创建和初始化状态机,并且状态机本身具有与 await 调用次数相等的状态数。


  • 转换方法Example

     [AsyncStateMachine(typeof(ExampleStateMachine))] public /*async*/ Task Example() { // Create a new instance of the ExampleStateMachine class ExampleStateMachine stateMachine = new ExampleStateMachine(); // Create a new AsyncTaskMethodBuilder and assign it to the taskMethodBuilder property of the stateMachine instance stateMachine.taskMethodBuilder = AsyncTaskMethodBuilder.Create(); // Set the currentState property of the stateMachine instance to -1 stateMachine.currentState = -1; // Start the stateMachine instance stateMachine.taskMethodBuilder.Start(ref stateMachine); // Return the Task property of the taskMethodBuilder return stateMachine.taskMethodBuilder.Task; }


  • 生成的状态机示例ExampleStateMachine

     [CompilerGenerated] private sealed class ExampleStateMachine : IAsyncStateMachine { public int currentState; public AsyncTaskMethodBuilder taskMethodBuilder; private TaskAwaiter taskAwaiter; public int paramInt; private int localInt; void IAsyncStateMachine.MoveNext() { int num = currentState; try { TaskAwaiter awaiter3; TaskAwaiter awaiter2; TaskAwaiter awaiter; switch (num) { default: localInt = paramInt; // Call the first synchronous method SyncMethodA(); // Create a task awaiter for a delay of 1000 milliseconds awaiter3 = Task.Delay(1000).GetAwaiter(); // If the task is not completed, set the current state to 0 and store the awaiter if (!awaiter3.IsCompleted) { currentState = 0; taskAwaiter = awaiter3; // Store the current state machine ExampleStateMachine stateMachine = this; // Await the task and pass the state machine taskMethodBuilder.AwaitUnsafeOnCompleted(ref awaiter3, ref stateMachine); return; } // If the task is completed, jump to the label after the first await goto Il_AfterFirstAwait; case 0: // Retrieve the awaiter from the taskAwaiter field awaiter3 = taskAwaiter; // Reset the taskAwaiter field taskAwaiter = default(TaskAwaiter); currentState = -1; // Jump to the label after the first await goto Il_AfterFirstAwait; case 1: // Retrieve the awaiter from the taskAwaiter field awaiter2 = taskAwaiter; // Reset the taskAwaiter field taskAwaiter = default(TaskAwaiter); currentState = -1; // Jump to the label after the second await goto Il_AfterSecondAwait; case 2: // Retrieve the awaiter from the taskAwaiter field awaiter = taskAwaiter; // Reset the taskAwaiter field taskAwaiter = default(TaskAwaiter); currentState = -1; break; Il_AfterFirstAwait: awaiter3.GetResult(); // Call the second synchronous method SyncMethodB(); // Create a task awaiter for a delay of 2000 milliseconds awaiter2 = Task.Delay(2000).GetAwaiter(); // If the task is not completed, set the current state to 1 and store the awaiter if (!awaiter2.IsCompleted) { currentState = 1; taskAwaiter = awaiter2; // Store the current state machine ExampleStateMachine stateMachine = this; // Await the task and pass the state machine taskMethodBuilder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine); return; } // If the task is completed, jump to the label after the second await goto Il_AfterSecondAwait; Il_AfterSecondAwait: // Get the result of the second awaiter awaiter2.GetResult(); // Call the SyncMethodC SyncMethodC(); // Create a new awaiter with a delay of 3000 milliseconds awaiter = Task.Delay(3000).GetAwaiter(); // If the awaiter is not completed, set the current state to 2 and store the awaiter if (!awaiter.IsCompleted) { currentState = 2; taskAwaiter = awaiter; // Set the stateMachine to this ExampleStateMachine stateMachine = this; // Await the task and pass the state machine taskMethodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); return; } break; } // Get the result of the awaiter awaiter.GetResult(); } catch (Exception exception) { currentState = -2; taskMethodBuilder.SetException(exception); return; } currentState = -2; taskMethodBuilder.SetResult(); } void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { /*...*/ } }

同步上下文

AwaitUnsafeOnCompleted调用中,将获取当前同步上下文SynchronizationContextSynchronizationContext是C#中的一个概念,用来表示控制一组异步操作执行的上下文。它用于协调跨多个线程的代码执行,并确保代码按特定顺序执行。 SynchronizationContext 的主要目的是提供一种在多线程环境中控制异步操作的调度和执行的方法。


在不同的环境中, SynchronizationContext有不同的实现。例如,在 .NET 中,有:


  • WPFSystem.Windows.Threading.DispatcherSynchronizationContext
  • WinFormsSystem.Windows.Forms.WindowsFormsSynchronizationContext
  • WinRTSystem.Threading.WinRTSynchronizationContext
  • System.Web.AspNetSynchronizationContext


Unity也有自己的同步上下文UnitySynchronizationContext ,它使我们能够通过绑定到 PlayerLoop API 使用异步操作。以下代码示例显示了如何使用Task.Yield()在每个帧中旋转对象:

 private async void Start() { while (true) { transform.Rotate(0, Time.deltaTime * 50, 0); await Task.Yield(); } }


另外个在 Unity 中施用 async/await 参与数据网络标准的典例:
 using UnityEngine; using System.Net.Http; using System.Threading.Tasks; public class NetworkRequestExample : MonoBehaviour { private async void Start() { string response = await GetDataFromAPI(); Debug.Log("Response from API: " + response); } private async Task<string> GetDataFromAPI() { using (var client = new HttpClient()) { var response = await client.GetStringAsync("//api.example.com/data"); return response; } } }


感谢UnitySynchronizationContext ,我们可以在异步操作完成后立即安全地使用UnityEngine方法(例如Debug.Log() ),因为此代码的执行将在主 Unity 线程中继续。

任务完成源<T>

此类允许您管理Task对象。它的创建是为了使旧的异步方法适应 TAP,但当我们想要围绕某个事件发生的一些长时间运行的操作包装一个Task时,它也非常有用。


在下面的示例中, taskCompletionSource中的Task对象将在开始后 3 秒后完成,我们将在Update方法中获取它的结果:

 using System.Threading.Tasks; using UnityEngine; public class Example : MonoBehaviour { private TaskCompletionSource<int> taskCompletionSource; private void Start() { // Create a new TaskCompletionSource taskCompletionSource = new TaskCompletionSource<int>(); // Start a coroutine to wait 3 seconds // and then set the result of the TaskCompletionSource StartCoroutine(WaitAndComplete()); } private IEnumerator WaitAndComplete() { yield return new WaitForSeconds(3); // Set the result of the TaskCompletionSource taskCompletionSource.SetResult(10); } private async void Update() { // Await the result of the TaskCompletionSource int result = await taskCompletionSource.Task; // Log the result to the console Debug.Log("Result: " + result); } }

取消令牌

C# 中使用取消令牌来表示应取消任务或操作。令牌被传递给任务或操作,任务或操作中的代码可以定期检查令牌以确定是否应停止任务或操作。这允许干净而优雅地取消任务或操作,而不是突然终止它。


关掉令牌一般是于粉丝应该关掉长时光运转的工作成就,甚至不需求需求该工作成就的状态,随后粉丝接口中的关掉按健。


整体模式类似于TaskCompletionSource的使用。首先,创建一个CancellationTokenSource ,然后将其Token传递给异步操作:

 public class ExampleMonoBehaviour : MonoBehaviour { private CancellationTokenSource _cancellationTokenSource; private async void Start() { // Create a new CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); // Get the token from the CancellationTokenSource CancellationToken token = _cancellationTokenSource.Token; try { // Start a new Task and pass in the token await Task.Run(() => DoSomething(token), token); } catch (OperationCanceledException) { Debug.Log("Task was cancelled"); } } private void DoSomething(CancellationToken token) { for (int i = 0; i < 100; i++) { // Check if the token has been cancelled if (token.IsCancellationRequested) { // Return if the token has been cancelled return; } Debug.Log("Doing something..."); // Sleep for 1 second Thread.Sleep(1000); } } private void OnDestroy() { // Cancel the token when the object is destroyed _cancellationTokenSource.Cancel(); } }


取消操作时,将抛出OperationCanceledException并将Task.IsCanceled属性设置为true

Unity 2022.2 中的新异步功能

重要的是要注意Task对象由 .NET 运行时管理,而不是由 Unity 管理,如果执行任务的对象被销毁(或者如果游戏在编辑器中退出播放模式),任务将继续运行,因为 Unity 已没有办法取消它。


您始终需要将await Task与相应的CancellationToken一起使用。这导致了一些代码冗余,在 Unity 2022.2 中出现了MonoBehaviour级别和整个Application级别的内置令牌。


让我们看看前面的例子在使用MonoBehaviour对象的destroyCancellationToken时是如何变化的:

 using System.Threading; using System.Threading.Tasks; using UnityEngine; public class ExampleMonoBehaviour : MonoBehaviour { private async void Start() { // Get the cancellation token from the MonoBehaviour CancellationToken token = this.destroyCancellationToken; try { // Start a new Task and pass in the token await Task.Run(() => DoSomething(token), token); } catch (OperationCanceledException) { Debug.Log("Task was cancelled"); } } private void DoSomething(CancellationToken token) { for (int i = 0; i < 100; i++) { // Check if the token has been cancelled if (token.IsCancellationRequested) { // Return if the token has been cancelled return; } Debug.Log("Doing something..."); // Sleep for 1 second Thread.Sleep(1000); } } }


我们不再需要手动创建CancellationTokenSource并在OnDestroy方法中完成任务。对于与特定MonoBehaviour无关的任务,我们可以使用UnityEngine.Application.exitCancellationToken 。这将在退出播放模式(在编辑器中)或退出应用程序时终止任务。

单任务

或许 .NET Tasks 安全使用的便利且效果厉害,但在 Unity 中安全使用的她们时具备很大的缺陷:


  • Task对象过于繁琐,造成多次分配。
  • Task与 Unity 线程(单线程)不匹配。


库在不使用线程或SynchronizationContext情况下绕过了这些限制。它通过使用基于UniTask<T>结构的类型实现了没有分配。


UniTask 需用 .NET 4.x js使用时型号信息,Unity 2018.4.13f1 是手官能够的至少型号信息。


您还可以使用扩展方法将所有AsyncOperations转换为UnitTask

 using UnityEngine; using UniTask; public class AssetLoader : MonoBehaviour { public async void LoadAsset(string assetName) { var loadRequest = Resources.LoadAsync<GameObject>(assetName); await loadRequest.AsUniTask(); var asset = loadRequest.asset as GameObject; if (asset != null) { // Do something with the loaded asset } } }


在此示例中, LoadAsset方法使用Resources.LoadAsync异步加载资产。然后使用AsUniTask方法将LoadAsync返回的AsyncOperation转换为可以等待的UniTask


和以前一样,您可以使用UniTask.WhenAllUniTask.WhenAny组织执行顺序的任意组合:

 using System.Threading; using Cysharp.Threading.Tasks; using UnityEngine; public class Example : MonoBehaviour { private async void Start() { // Start two Tasks and wait for both to complete await UniTask.WhenAll(Task1(), Task2()); // Start two Tasks and wait for one to complete await UniTask.WhenAny(Task1(), Task2()); } private async UniTask Task1() { // Do something } private async UniTask Task2() { // Do something } }


在 UniTask 中,还有一个SynchronizationContext的实现,称为UniTaskSynchronizationContext ,可以用来替代UnitySynchronizationContext以获得更好的性能。

等待API

在 Unity 2023.1 的第一个 alpha 版本中,引入了Awaitable类。 Awaitable 协程是异步/等待兼容的类似任务的类型,旨在在 Unity 中运行。与 .NET 任务不同,它们由引擎而不是运行时管理。

 private async Awaitable DoSomethingAsync() { // awaiting built-in events await Awaitable.EndOfFrameAsync(); await Awaitable.WaitForSecondsAsync(); // awaiting .NET Tasks await Task.Delay(2000, destroyCancellationToken); await Task.Yield(); // awaiting AsyncOperations await SceneManager.LoadSceneAsync("path/to/scene.unity"); // ... }


它们可以被等待并用作异步方法的返回类型。与System.Threading.Tasks相比,它们不那么复杂,但采用基于 Unity 特定假设的性能增强捷径。


下是与 .NET Tasks 比较的主要区分:


  • Awaitable对象只能等待一次;它不能被多个异步函数等待。
  • Awaiter.GetResults()在完成之前不会阻塞。在操作完成之前调用它是未定义的行为。
  • 永远不要捕获ExecutionContext 。出于安全原因,.NET 任务在等待时捕获执行上下文,以便跨异步调用传播模拟上下文。
  • 永远不要捕获SynchronizationContext 。协程延续从引发完成的代码同步执行。在大多数情况下,这将来自 Unity 主框架。
  • Awaitables 是池化对象,以防止过度分配。这些是引用类型,因此可以跨不同的堆栈引用它们,高效地复制它们等等。 ObjectPool已得到改进,以避免在异步状态机生成的典型获取/释放序列中进行Stack<T>边界检查。


要获得长时间操作的结果,您可以使用Awaitable<T>类型。您可以使用AwaitableCompletionSourceAwaitableCompletionSource<T>管理Awaitable的完成类似于TaskCompletitionSource

 using UnityEngine; using Cysharp.Threading.Tasks; public class ExampleBehaviour : MonoBehaviour { private AwaitableCompletionSource<bool> _completionSource; private async void Start() { // Create a new AwaitableCompletionSource _completionSource = new AwaitableCompletionSource<bool>(); // Start a coroutine to wait 3 seconds // and then set the result of the AwaitableCompletionSource StartCoroutine(WaitAndComplete()); // Await the result of the AwaitableCompletionSource bool result = await _completionSource.Awaitable; // Log the result to the console Debug.Log("Result: " + result); } private IEnumerator WaitAndComplete() { yield return new WaitForSeconds(3); // Set the result of the AwaitableCompletionSource _completionSource.SetResult(true); } }


有时需要执行大量计算,这可能会导致游戏卡顿。为此,最好使用 Awaitable 方法: BackgroundThreadAsync()MainThreadAsync() 。它们允许您退出主线程并返回主线程。

 private async Awaitable DoCalculationsAsync() { // Awaiting execution on a ThreadPool background thread. await Awaitable.BackgroundThreadAsync(); var result = PerformSomeHeavyCalculations(); // Awaiting execution on the Unity main thread. await Awaitable.MainThreadAsync(); // Using the result in main thread Debug.Log(result); }


这种,Awaitables 解决了适用 .NET Tasks 的短处,还可以处理 PlayerLoop 故事和 AsyncOperations。

结论

我们大家是可以看看,随之Unity的发展壮大,组织结构异步操作的的工具软件变得也变得多:
实行协程承若书.NET 成就单每日任务内嵌撤销令牌等候API
5.6





2017.1




2018.4



2022.2


2023.1


各位就已经综合考虑了 Unity 中异步编程序的拥有主要方法。依照您的世界任务的繁复性和您安全用到的 Unity 发行版,您需要安全用到区间大面积的技巧,从协程和承诺书到世界任务和等候,以抓实您的小游戏的很卡无逢地使用小游戏的。感谢信看书,各位期许您的下1部杰作。
바카라사이트 바카라사이트 온라인바카라