协程,又称微线程,纤程。英文名Coroutine
什么是协同程序?什么是协程?
unity协程是一个能够暂停协程执行,暂停后立即返回主函数,执行主函数剩余的部分,直到中断指令完成后,从中断指令的下一行继续执行协程剩余的函数。函数体全部执行完成,协程结束
由于中断指令的出现,使得可以将一个函数分割到多个帧里去执行
性能:
在性能上相比于一般函数没有更多的开销
协程的好处:
让原来要使用异步 + 回调方式写的非人类代码, 可以用看似同步的方式写出来。
能够分步做一个比较耗时的事情,如果需要大量的计算,将计算放到一个随时间进行的协程来处理,能分散计算压力
协程的坏处:
协程本质是迭代器,且是基于unity生命周期的,大量开启协程会引起gc
如果同时激活的协程较多,就可能会出现多个高开销的协程挤在同一帧执行导致的卡帧
协程书写时的性能优化:
常见的问题是直接new 一个中断指令,带来不必要的 GC 负担,可以复用一个全局的中断指令对象,优化掉开销;在 Yielders.cs 这个文件里,已经集中地创建了上面这些类型的静态对象
这个链接分析了一下 https://blog.csdn.net/liujunjie612/article/details/70623943
协程的基本形式
IEnumerator MyCoroutine()
{
// Do Something
yield return 0;
}
关键词 IEnumerator
首先我们来看看IEumerator的定义:
public interface IEnumerator
{
bool MoveNext();
void Reset();
Object Current{get;}
}
从定义中我们可以发现,这是一个迭代器,有三个基本的操作:Current/MoveNext/Reset,
这儿简单说一下其操作的过程。在常见的集合中,我们使用foreach这样的枚举操作的时候,最开始,枚举数被定为在集合的第一个元素前面,Reset操作就是将枚举数返回到此位置。
迭代器在执行迭代的时候,首先会执行一个 MoveNext, 如果返回true,说明下一个位置有对象,然后此时将Current设置为下一个对象,这时候的Current就指向了下一个对象。
关键词 yield
yield关键词后一般有两种基本的表达式:
yield return <expression>
yield break
yield break为跳出协程的操作,一般用于报错或跳出协程yield return是常用表达式,后面的expression有以下几个常见的实例:
- WWW : 常见的web操作,在每帧末调用,会检查isDone/isError,如果true,则 call MoveNext
- WaitForSeconds( ): 等待指定秒数,在该帧(延迟过后的那一帧)所有update( )函数调用完后执行。即等待给定时间周期, 受Time.timeScale影响,当Time.timeScale = 0f 时,yield return new WaitForSecond(x) 将不会满足
- null: 在下一帧所有的Update()函数调用过之后执行
- WaitForEndOfFrame: 等待帧结束,即等待渲染周期循环结束后执行
- WaitForFixedUpdate: 等待一个固定帧,即等待物理周期循环结束后执行
- StartCoroutine( ): 等待一个新协程暂停
协程的开始与结束
开始一个协同程序
- StartCoroutine(IEnumerator routine);
- StartCoroutine(Example( ));(注意方法名后加括号,参数可写在括号里)
- 优点:灵活,性能开销小
- 缺点:无法单独的停止这个协程,如果需要停止这个协程只能等待协同程序运行完毕或则使用StopAllCoroutine( );方法
- StartCoroutine (methodName:string, value : object = null);
- StartCoroutine(“string methodName”,value); (注意双引号,value为想传递的参数)
- 优点:可以直接通过传入协同程序的方法名来停止这个协程:StopCoroutine(“string methodName”); (注意双引号)
- 缺点:性能的开销较大,只能传递一个参数。
停止协同程序
- StopCoroutine(string methodName);
- StopAllCoroutine();
- 设置gameobject.active为false时可以终止协同程序,但是再次设置为true后协程不会再启动
Unity中协程的执行时间
协程不是线程,不是异步执行
协程和monobehaviour的update( )函数一样也是在主线程中执行
Unity在每一帧都会处理对象上的协程,也就是说,协程跟update( )一样都是Unity每帧会去处理的函数
按照Unity程序生命周期图示,除WaitForEndOfFrame( )以外的其他协程将于每次的update( )和lateupdate( )之间执行
其他
协程的执行顺序:
开始协程->执行协程->遇到中断指令中断协程->返回上层函数继续执行上层函数的下一行代码->中断指令结束后,继续执行中断指令之后的代码->协程结束
协程可以嵌套协程吗?
可以,yield return StartCoroutine就是,执行顺序是:
子协程中断后,会返回父协程,父协程暂停,返回父协程的上级函数。
决定父协程结束的标志是子协程是否结束,当子协程结束后返回父协程执行其后的代码才算结束。
同一时刻同一脚本实例中能有多少个运行的协程?
在一个MonoBehaviour提供的主线程里只能有一个处于运行状态的协程。因为协程不是线程,不是并行的。同一时刻、一个脚本实例中可以有多个暂停的协程,但只有一个运行着的协程
协程和线程的区别?
线程是利用多核达到真正的并行计算,缺点是会有大量的锁、切换、等待的问题,而协程是非抢占式,需要用户自己释放使用权来切换到其他协程, 因此同一时间其实只有一个协程拥有运行权, 相当于单线程的能力。
协程是 C# 线程的替代品, 是 Unity 不使用线程的解决方案。
使用协程不用考虑同步和锁的问题
多个协程可以同时运行,它们会根据各自的启动顺序来更新
其他注意点:
1、IEnumerator 类型的方法不能带 ref 或者 out 型的参数,但可以带被传递的引用
2、在函数 Update 和 FixedUpdate 中不能使用 yield 语句,否则会报错, 但是可以启动协程
3、在一个协程中,StartCoroutine()和 yield return StartCoroutine()是不一样的。
前者仅仅是开始一个新的Coroutine,这个新的Coroutine和现有Coroutine并行执行。
后者是返回一个新的Coroutine,是一个中断指令,当这个新的Coroutine执行完毕后,才继承执行现有Coroutine