在很多编程语言中都支持协程,例如在我们之前提到的 lua 中,协程是一个虚拟的线程技术。
# 简述
想一想我们平时购买电脑提及的,cpu 是四核八线程,其实 cpu 原先只能处理处理一件事,也就是说 cpu 默认是一个核心对应一条线程的,但是如果我们需要同时处理多个任务,而我们并没有那么多的线程数量。
然后前人们就提出了虚拟线程的概念,将 cpu 的单个线程,虚拟出多条线程,也就有了我们四核八线程,八核十六线程等的概念;在应用程序这边也有了线程和进程的概念,在把进程再细分,虚拟化软件的线程就得到了协程的概念。
至此你知道,协程就是对线程的再细分,是线程的再虚拟化即可。
# unity 的协程
虽然说协程的概念并不新颖,但在现在大趋势的互联网开发领域可能很少涉及到 协程
这个词汇。
协程就是协力去完成一件事,这很容易想到多线程的概念,例如我们进行一次网络请求我们需要等待 response 之后才能下一步操作,此时我们就会用到 互斥锁
、 线程安全
等概念。
在 unity 中或者说在游戏引擎中,由于受到游戏主循环线程的制约,所以不能确保多线程的安全性,此时在同一线程下继续使用协程来承担多线程的工作就显得尤为重要。
(unity 也退出了以性能优先的 ECS 模式,摒弃 Mono 框架,实现了可多线程协助开发的开发模式)
下面我们就主要以 unity 的协程详细介绍。
# unity 协程示例
看这个例子:
// 创建协程 | |
private IEnumerator Cor1() | |
{ | |
yield return new WaitForSeconds(2f); | |
Debug.Log("2s到了"); | |
} | |
// 启动协程 | |
StartCoroutine(Cor1()); |
这是一个简单示例,可以看到协程需要返回一个 IEnumerator
可迭代对象,这本来是 Csharp 中的迭代器模式的实现,在 unity 中 unity 以此为原型实现了协程。
# 协程的参数
在上面我们使用了 new WaitForSeconds()
,这表示等待指定的时间。【注意 WaitForSeconds 与 Time.Scale 相关】
在上面使用的 WaitForSeconds 之外还有许多的参数,这些参数要么需要花费时间,要么返回 bool,总之就是需要确定一个 moveNext。
# 协程的使用情况
- 用于不确定的时长情况(例如:网络请求,读写文件)
- 用于延迟执行
- 可当做简易计时器使用(例如:生产一批敌人)
# 协程的嵌套
协程支持嵌套,如下是一个利用协程实现的巡逻的简单实现。
注:在 unity 中,协程返回 0 或 null 表示等待下一帧。
using System; | |
using System.Collections; | |
using UnityEngine; | |
namespace Coroutines | |
{ | |
// 协程测试 | |
public class CoroutTest : MonoBehaviour | |
{ | |
public Transform[] wayPoints; | |
private bool isLoop; | |
private void Start() | |
{ | |
isLoop = true; | |
StartCoroutine(StartLoop()); | |
} | |
private IEnumerator StartLoop() | |
{ | |
do | |
{ | |
foreach (var point in wayPoints) | |
{ | |
yield return StartCoroutine(MoveTarget(point.position)); | |
} | |
} while (isLoop); | |
} | |
private IEnumerator MoveTarget(Vector3 target) | |
{ | |
while (transform.position!=target) | |
{ | |
transform.position = Vector3.MoveTowards(transform.position, target, 3 * Time.deltaTime); | |
yield return 0; | |
} | |
} | |
} | |
} |
# 让协程动起来
StartCoroutine(nameof(StartLoop));
以字符串形式启动协程,能够在外部停止协程,无法传递参数。StartCoroutine(StartLoop);
以迭代器形式启动协程,能够传递参数,无法在外部使用 stop 停止协程。
# 让协程停下来
协程本质是一个迭代器,当 moveNext 为 false 时即认为协程中所有的项目都已经执行完毕。
在 unity 中有以下几种方式停止协程:
- StopCoroutine () 注意此方式只能停止以字符串形式启动的协程 【在协程外部使用】
- yield break 跳出协程【在协程内部使用】
- 通过逻辑来停止 【使其协程执行条件不满足】
- 设置物体不激活 【再次激活协程也不会恢复】
- StopAllCoroutine () 终止所有协程
如上面协程嵌套的例子中,如果我们想要协程停止:
- 设置 isLoop=false;让其在执行一次后不满足条件自动停下
- 在协程内部 break
if (transform.position==wayPoints[2].position) | |
{ | |
yield break; | |
} |
- 在协程外部 stop
StopCoroutine(nameof(StartLoop)); |
# 协程的设计思想
# 协程是否取代 update?
通过上面的例子,你大可发现,协程其实是对 update 的另一种实现,我们甚至可以只使用协程而不使用任何 update 和 fixedUpdate 完成程序的编写。
但我们如果这样做不是本末倒置了吗?协程是 unity 推出的延迟执行的一种范式,其还是基于 update 为原理的上层实现。
# 使用协程会大大提升程序效率吗?
不会,协程本质上还是在一条线程上,尽管可以多条协程并行,但这些协程始终还是运行在一条线程上,速度和效率并不会得到很大的提升。反而开辟多条线程并行,线程需要多多协程的状态保持监听,在协程大量结束时会触发大量 GC 回收,可能会降低程序的运行效率。
# 总结
协程是运行在线程上的线程,其运作方式任然基于单线程,并不会因为使用协程提高程序的运行效率,但协程方便的书写方式,强大的功能能够提高我们作为开发者的开发效率。
从某种意义上来讲,协程更像是一个精美的语法糖