2.协程和Task
2.1 知识点
同步与异步的基本概念
同步
同步调用意味着方法1调用方法2时,必须等待方法2执行完毕后才能继续执行方法1。这样的调用模式虽然简单,但容易导致阻塞,降低程序响应速度。异步
异步调用允许方法1与方法2在代码中同时或交替执行。调用后方法1不会等待方法2执行完成,而是立即执行后续代码。这样可以提高程序响应性,充分利用资源,尤其在处理耗时操作时尤为有效。
异步不等于多线程
需要注意的是,异步编程仅是解决阻塞问题的编程模型,它并不意味着一定会使用多线程:
- 在 Unity 中,协程通过单线程的 PlayerLoop 实现异步效果,并没有真正创建新的线程。
- C# 的 Task 有时会涉及线程池切换,但其设计初衷主要是为了异步等待,而非并发处理。
因此,异步的优势在于不阻塞当前线程,而多线程则涉及额外的线程管理与同步问题。
Unity 协程存在的问题
传统的 Unity 协程虽然简单易用,但也存在一些局限性:
依赖 MonoBehaviour
协程只能运行在继承了 MonoBehaviour 的组件上,无法在无此组件的上下文中使用。异常处理缺失
协程中异常往往无法被捕获或传递,导致调试与错误处理变得困难。返回值获取困难
协程无法像普通函数那样直接返回值,需要额外机制来获取结果,增加了编码复杂性。
C# 原生 Task 的局限性
虽然 C# 的 Task 为异步编程提供了一定支持,但在 Unity 中使用时也存在一些不足:
资源消耗较高
Task 由于涉及线程切换和上下文捕获,可能会带来额外的内存分配和性能开销。跨线程处理复杂
Task 常常需要手动管理线程同步和上下文切换,与 Unity 的单线程模型不完全兼容,增加了开发和调试的难度。
测试脚本示例
以下示例代码展示了使用 Unity 协程和 C# Task 进行异步操作的效果,帮助理解异步调用不会阻塞主线程:
using System.Collections;
using System.Threading.Tasks;
using UnityEngine;
public class Lesson02_协程和Task : MonoBehaviour
{
private bool _coroutineEnd = false;
private bool _taskEnd = false;
async void Start()
{
Debug.LogWarning("Start :" + Time.time);
StartCoroutine(CoroutineTest());
await TaskTest();
}
IEnumerator CoroutineTest()
{
Debug.LogWarning("Coroutine Start:" + Time.time);
yield return new WaitForSeconds(1);
Debug.LogWarning("Coroutine End:" + Time.time);
_coroutineEnd = true;
}
async Task<int> TaskTest()
{
Debug.LogWarning("Task Start:" + Time.time);
await Task.Delay(1000);
Debug.LogWarning("Task End:" + Time.time);
_taskEnd = true;
return 1;
}
void Update()
{
if (_coroutineEnd 22 _taskEnd)
{
return;
}
Debug.Log("Update执行了" + Time.time);
}
}
在此脚本中:
- 协程通过
StartCoroutine
启动并在等待 1 秒后结束。 - Task 异步方法使用
await Task.Delay(1000)
实现等待 1 秒,然后结束。但实际上可能并不准确。 - 在
Update
方法中,可以看到主线程持续运行,不论协程或 Task 是否完成,验证了异步调用不会阻塞主线程。
UniTask 的必要性与优势
为了解决上述问题,UniTask 专为 Unity 开发设计,其优势包括:
零 GC 分配
采用基于值类型的UniTask<T>
和自定义AsyncMethodBuilder
,大幅降低内存分配与 GC 压力。单线程高效运行
完全基于 Unity 的 PlayerLoop 运行,无需依赖线程池,避免了不必要的线程切换和同步复杂性。直接支持 Unity 异步对象
能够直接对 Unity 的AsyncOperation
、协程等进行await
,简化异步代码的编写。兼容性与易用性
UniTask 保留了 C# 原生 Task/ValueTask 的语义,同时在接口上进行了扩展,使异步编程更加直观和高效,极大地提升了开发效率。
总结
- 同步调用要求前一个方法执行完毕后才能继续,容易造成阻塞。
- 异步调用允许方法之间并行或交替执行,显著提高了响应性和资源利用率。
- 传统 Unity 协程存在依赖 MonoBehaviour、异常处理和返回值获取等问题。
- C# Task 虽支持异步,但在 Unity 中可能因资源消耗和线程切换复杂性而影响性能。
- UniTask 针对 Unity 环境进行了专门优化,实现零 GC、无多线程切换开销,简化了异步操作的使用,使开发者能够以更直观高效的方式编写异步代码。
2.2 知识点代码
Lesson02_协程和Task.cs
using System.Collections;
using System.Threading.Tasks;
using UnityEngine;
public class Lesson02_协程和Task : MonoBehaviour
{
private bool _coroutineEnd = false;
private bool _taskEnd = false;
async void Start()
{
Debug.LogWarning("Start :" + Time.time);
StartCoroutine(CoroutineTest());
await TaskTest();
}
IEnumerator CoroutineTest()
{
Debug.LogWarning("Coroutine Start:" + Time.time);
yield return new WaitForSeconds(1);
Debug.LogWarning("Coroutine End:" + Time.time);
_coroutineEnd = true;
}
async Task<int> TaskTest()
{
Debug.LogWarning("Task Start:" + Time.time);
await Task.Delay(1000);
Debug.LogWarning("Task End:" + Time.time);
_taskEnd = true;
return 1;
}
void Update()
{
if (_coroutineEnd && _taskEnd)
{
return;
}
Debug.Log("Update执行了" + Time.time);
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com