概述
1)协程概念
协同程序(Coroutine)简称协程,是伴随主线程一起运行的程序片段,是一个能够暂停执行的函数,用于解决程序并行问题,主要是将主线程中耗时的逻辑分时分步的执行,他并不是开辟一个新的线程去执行,而是用线程来承载运行
协程是一个能够暂停执行的函数,在收到中断指令后暂停执行,并立即返回主函数,执行主函数剩余的部分,直到中断指令完成后,从中断指令的下一行继续执行协程剩余的部分。函数体全部执行完成,协程结束。协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。由于中断指令的出现,使得可以将一个函数分割到多个帧里执行。
注:Unity是支持多线程的,只是新创建的线程是无法访问Unity相关对象的内容
2)中断指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| yield return WaitForFixedUpdate();
yield return null;
yield return 6;
yield return new WaitForSeconds(seconds);
yield return WaitForSecondsRealtime(seconds);
yield return new WWW(url);
yield return StartCoroutine();
yield return WaitWhile();
yield return new WaitForEndOfFrame();
|
补充:中断对象可以使用静态全局变量,避免产生过多临时对象、频繁触发 GC。
3)协程的执行周期
4)协程与线程的区别
- 一个线程可以有多个协程;
- 线程是协程的资源,协程通过 Interceptor 来间接使用线程这个资源;
- 线程是同步机制,必须等待方法执行完才能返回执行后续方法;协程是异步机制,不需要等方法执行完就可以返回继续执行后续方法;
- 线程是抢占式,进行线程切换,需要使用锁机制,多线程执行顺序具有一定随机性;协程是非抢占式的,多协程执行顺序由其启动顺序决定。
协程的使用
1)创建协程
1 2 3 4 5 6 7 8 9 10 11
| private IEnumerator CorutineTest() { Debug.Log("CorutineTest, 1"); yield return null; Debug.Log("CorutineTest, 2"); yield return new WaitForSeconds(0.05f); Debug.Log("CorutineTest, 3"); yield return new WaitForFixedUpdate(); Debug.Log("CorutineTest, 4"); yield return new WWW("https://mazwai.com/download_new.php?hash=b524357ef93c1e6ad0245c04c721e479"); Debug.Log("CorutineTest, 5"); }
|
2)启动协程
1 2 3 4 5 6 7 8 9 10 11
| private void Start() { StartCoroutine(CorutineTest()); StartCoroutine(CorutineTest("arg")); StartCoroutine("CorutineTest"); StartCoroutine("CorutineTest", "arg"); IEnumerator corutin = CorutineTest(); StartCoroutine(corutin); }
|
3)停止协程
1 2 3 4 5
| public void StopCoroutine(Coroutine routine); public void StopCoroutine(IEnumerator routine); public void StopCoroutine(string methodName); public void StopAllCoroutines(); yield break;
|
4)Start 协程
1 2 3
| private IEnumerator Start() { yield return StartCoroutine(CorutineTest()); }
|
注:协程只有在脚本失活时,不受影响,当脚本移除,游戏物体失活、销毁时都会结束
协程的原理
yield return的作用是在执行到这行代码之后,将控制权立即交还给外部。yield return之后的代码会在外部代码再次调用MoveNext时才会执行,直到下一个yield return——或是迭代结束。所以可以把协程抽象成以下情况:
- 协程调度器IEnumerator,是一个函数对象的容器[函数代码1, 函数代码2,…,函数代码n]
- 协程函数本体 yield抽取函数代码,放入IEnumerator容器中
证明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| IEnumerator testCoroutine() { Debug.Log("test coroutine"); yield return 1; Debug.Log(100); yield return 50; }
void Start(){ IEnumerator testCor = this.testCoroutine(); Debug.Log("== start =="); while(testCor.MoveNext()){ Debug.Log(testCor.Current); } Debug.Log("== end =="); }
|
结果:
模拟协程实现全过程
从上面知道协程的本质原理,但是所有代码都是在一帧执行的,真正的协程不是这样的,现在我们来模拟协程,就只要在上面的基础上实现将IEnumerator容器内的函数每隔一帧触发一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| void Start(){ IEnumerator testCor = this.testCoroutine(); Debug.Log("== start =="); My_StartCoroutine(testCoroutine); Debug.Log("== end =="); }
public void My_StartCoroutine(IEnumerator e){ this.myEnum = e; }
IEnumerator testCoroutine() { Debug.Log("test coroutine"); yield return 1; Debug.Log(100); yield return 50; }
void LastUpdate(){ if(this.myEnum != null){ Debug.Log("start lastupdate"); if(!this.myEnum.MoveNext()){ this.myEnum = null; } Debug.Log("end lastupdate"); } }
|
结果:
模拟协程等待
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void LastUpdate(){ if(this.myEnum != null){ if(this.myEnum.Current is MyWaitForSeconds){ MyWaitForSeconds wait = this.myEnum.Current as MyWaitForSeconds; wait.update(); if(!wait.isOver()){ return; } } Debug.Log("start lastupdate"); if(!this.myEnum.MoveNext()){ this.myEnum = null; } Debug.Log("end lastupdate"); } }
|
所以类似的像UnityWebRequest
对象的Send
方法,实际上也是每帧判断这个方法返回值AsyncOperation
对象里面的isDone
方法,为true
执行下一个函数,为false
下一帧继续判断
1 2 3
| UnityWebRequest req = UnityWebRequest.Get("..."); yield return req.Send(); Debug.Log("下载成功:"+req.downloadedBytes);
|
引用
https://blog.csdn.net/m0_37602827/article/details/126679460