记得之前开始实习的时候,当时要我写网络层的结构,用到了协程,当时有点懵,完全不知道Unity协程的执行机制是怎么样的,只是知道函数的返回值是IEnumerator类型,函数中使用yield return ,就可以通过StartCoroutine调用了。后来也是一直稀里糊涂地用,上网google些基本都是例子,很少能帮助深入理解Unity协程的原理的。
本文只是从Unity的角度去分析理解协程的内部运行原理,而不是从C#底层的语法实现来介绍(后续有需要再进行介绍),一共分为三部分:
线程(Thread)和协程(Coroutine)
Unity中协程的执行原理
IEnumerator & Coroutine
虽然之前自己对协程还算有点了解了,但是对Unity如何执行协程的还是一片空白,在UnityGems.com上看到两篇讲解Coroutine,如数家珍,当我看到Advanced Coroutine后面的Hijack类时,顿时觉得十分精巧,眼前一亮,遂动了写文分享之。
线程(Thread)和协程(Coroutine)
D.S.Qiu觉得使用协程的作用一共有两点:1)延时(等待)一段时间执行代码;2)等某个操作完成之后再执行后面的代码。总结起来就是一句话:控制代码在特定的时机执行。
很多初学者,都会下意识地觉得协程是异步执行的,都会觉得协程是C# 线程的替代品,是Unity不使用线程的解决方案。
所以首先,请你牢记:协程不是线程,也不是异步执行的。协程和 MonoBehaviour 的 Update函数一样也是在MainThread中执行的。使用协程你不用考虑同步和锁的问题。
Unity中协程的执行原理
UnityGems.com给出了协程的定义:
A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.
即协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。
Unity在每一帧(Frame)都会去处理对象上的协程。Unity主要是在Update后去处理协程(检查协程的条件是否满足)
协程跟Update()其实一样的,都是Unity每帧对会去处理的函数(如果有的话)。如果MonoBehaviour 是处于激活(active)状态的而且yield的条件满足,就会协程方法的后面代码。还可以发现:如果在一个对象的前期调用协程,协程会立即运行到第一个 yield return 语句处,如果是 yield return null ,就会在同一帧再次被唤醒。
- using UnityEngine;
- using System.Collections;
- public class TestCoroutine : MonoBehaviour {
- private bool isStartCall = false; //Makesure Update() and LateUpdate() Log only once
- private bool isUpdateCall = false;
- private bool isLateUpdateCall = false;
- // Use this for initialization
- void Start () {
- if (!isStartCall)
- {
- Debug.Log("Start Call Begin");
- StartCoroutine(StartCoutine());
- Debug.Log("Start Call End");
- isStartCall = true;
- }
- }
- IEnumerator StartCoutine()
- {
- Debug.Log("This is Start Coroutine Call Before");
- yield return new WaitForSeconds(1f);
- Debug.Log("This is Start Coroutine Call After");
- }
- // Update is called once per frame
- void Update () {
- if (!isUpdateCall)
- {
- Debug.Log("Update Call Begin");
- StartCoroutine(UpdateCoutine());
- Debug.Log("Update Call End");
- isUpdateCall = true;
- }
- }
- IEnumerator UpdateCoutine()
- {
- Debug.Log("This is Update Coroutine Call Before");
- yield return new WaitForSeconds(1f);
- Debug.Log("This is Update Coroutine Call After");
- }
- void LateUpdate()
- {
- if (!isLateUpdateCall)
- {
- Debug.Log("LateUpdate Call Begin");
- StartCoroutine(LateCoutine());
- Debug.Log("LateUpdate Call End");
- isLateUpdateCall = true;
- }
- }
- IEnumerator LateCoutine()
- {
- Debug.Log("This is Late Coroutine Call Before");
- yield return new WaitForSeconds(1f);
- Debug.Log("This is Late Coroutine Call After");
- }
- }
得到日志输入结果如下:
然后将yield return new WaitForSeconds(1f);改为 yield return null; 发现日志输入结果和上面是一样的,没有出现上面说的情况:
- using UnityEngine;
- using System.Collections;
- public class TestCoroutine : MonoBehaviour {
- private bool isStartCall = false; //Makesure Update() and LateUpdate() Log only once
- private bool isUpdateCall = false;
- private bool isLateUpdateCall = false;
- // Use this for initialization
- void Start () {
- if (!isStartCall)
- {
- Debug.Log("Start Call Begin");
- StartCoroutine(StartCoutine());
- Debug.Log("Start Call End");
- isStartCall = true;
- }
- }
- IEnumerator StartCoutine()
- {
- Debug.Log("This is Start Coroutine Call Before");
- yield return null;
- Debug.Log("This is Start Coroutine Call After");
- }
- // Update is called once per frame
- void Update () {
- if (!isUpdateCall)
- {
- Debug.Log("Update Call Begin");