经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 游戏设计 » 查看文章
Unity C#笔记 协程
来源:cnblogs  作者:KillerAery  时间:2019/3/4 8:43:27  对本文有异议

什么是协程

协同程序,在主程序运行的同时,开启另外一段逻辑处理,来协同当前程序的执行。
可能看了这段文字介绍还是有点模糊,其实可以用多线程来比较。

多线程

多线程,顾名思义,多条同时执行的线程。
最初,多线程的诞生是为了解决IO阻塞问题,如今多线程可以解决许多同样需要异步方法的问题(例如网络等)。
所谓异步,通俗点讲,就是我走我的线程,你走你的线程。当某个线程阻塞时,另一个线程不会受影响继续执行。

需要认识到的是,多线程并不是真正意义上的多线程。
它的实际是将一个时间段分成若干个时间片,每个线程只运行一个时间片。

(如图,将执行步骤切分成极小的粒度,然后依次运行)

由于粒度过小,程序执行效果跟真正意义上的并行执行效果基本一致。

多线程的缺陷

然而多线程有一个坏处,就是可能造成共享数据的冲突。

假如在上图,有一个变量i = 0, Step1_1的操作是进行++i操作,Step2_1的操作是进行--i操作。
我们预期最终结果i为0。

但由于操作切分得过小,可能会发生这样顺序的事:

  • Step1_1:访问i, 将0存到寄存器
  • Step2_1:访问i, 将0存到寄存器
  • Step1_1:++i, 得到1
  • Step2_1:--i, 得到-1
  • Step1_1:将1写入到i的内存
  • Step2_1:将-1写入到i的内存
  • 最终i的值为-1

当然多线程的冲突也有解决方案: 互斥锁....

但是这些多多少少会付出额外的代价,让程序变得臃肿。

协程

CPU有多条线程,一条线程可以有多个协程。

协程跟多线程类似,也有类似异步的效果(注意不是真正的异步)。
只不过它的切分粒度不是基于时间片,而是基于我们的定义,而且往往粒度更大。

粒度是取决于自己定义什么时候让协程挂起(一般来说都是偏向划分出尽可能小的粒度):

  1. IEnumerator Test()
  2. {
  3. //一些挂起
  4. for(int i = 0; i<1000 ; ++i){
  5. ans += i;
  6. yield return 0;//yield return 0:挂起,下一帧再来从这个位置继续执行。
  7. }
  8. j+=2;
  9. yield return 0;
  10. ++j;
  11. yield return 0;
  12. }
  1. IEnumerator Test()
  2. {
  3. //一些不好的挂起
  4. for(int i = 0; i<1000 ; ++i){
  5. ans += i;
  6. }
  7. yield return 0;
  8. ++j;
  9. j+=2;
  10. ++j;
  11. yield return 0;
  12. }

倘若划分的粒度过大,甚至让协程阻塞(死循环),那么协程所在的整个线程也会阻塞。
因此说协程可以有类似异步的效果,但是不是真正的异步。

由于它的粒度相对多线程的大很多,所以往往很少出现冲突现象:

在上面多线程的例子里,使用协程则可以这样:

  • Step1_1: 执行完++i, 此时i=1
  • Step2_1: 执行完--i, 此时i=0
  • 最终i的值为0

所以协程的一大好处就是可以避免数据访问冲突的问题。

对于保证不会阻塞的并行操作且并行性要求低的并行操作,可以用协程。

协程的使用场景

协程常用于延时执行等控制时间轴的操作:N秒后调用指定函数。

协程使用示例

首先编写好协程函数

  1. IEnumerator TestWaitForSeconds()
  2. {
  3. //3s后执行Debug.Log;
  4. yield return new WaitForSeconds(3.0f);
  5. Debug.Log("启动协程3s后");
  6. }

然后在某个地方使用StartCoroutine(TestWaitForSeconds())或者StartCoroutine("TestWaitForSeconds")

  1. //启动协程:3s后执行Debug.log
  2. StartCoroutine(TestWaitForSeconds());
  3. //启动后,继续往下执行
  4. ...

Invoke的缺陷

此外,还有个一样也是用于延时调用的函数,叫Invoke

  1. Invoke("test",2.0f); \\延时2秒后执行函数test

但是其函数必须是空类型返还值,还必须得是在当前类里面的方法。

一般来说,用协程来解决这样的问题已经绰绰有余,而且还有更安全的调用方法而不是只用string类型作为参数的方法,因此没必要使用Invoke。

协程使用方法

开启协程

  1. StartCoroutine(string methodName);
  • 参数是方法名(字符串类型),此方法可以包含一个参数。
  • 形参方法可以有返回值
  1. StartCoroutine(IEnumerator method);
  • 参数是方法(TestMethod()),此方法中可以包含多个参数。
  • IEnumrator类型的方法不能含有ref或者out类型的参数,但可以含有被传递的引用
  • 形参方法必须有返回值,且返回值类型为IEnumrator,返回值使用(yield retuen +表达式或者值,或者 yield break)语句

终止协程

  1. StopCoroutine(string methodName);//终止指定的协程
  • 在程序中调用StopCoroutine()方法只能终止以字符串形式启动的协程
  1. StopAllCoroutine();//终止所有协程

挂起

  1. //挂起,程序遇到yield关键字时会被挂起,暂停执行,等待条件满足时从当前位置继续执行
  2. yield;
  3. //程序在下一帧中从当前位置继续执行
  4. yield return 0;
  5. //程序在下一帧中从当前位置继续执行
  6. yield return null;
  7. //程序等待N帧之后从当前位置继续执行
  8. yield return N;
  9. //程序等待N秒后从当前位置继续执行
  10. yield return new WaitForSeconds(N);
  11. //在所有的渲染以及GUI程序执行完成后从当前位置继续执行
  12. yield new WaitForEndOfFrame();
  13. //所有脚本中的FixedUpdate()函数都被执行后从当前位置继续执行
  14. yield new WaitForFixedUpdate();
  15. //等待一个网络请求完成后从当前位置继续执行
  16. yield return WWW;
  17. //等待一个xxx的协程执行完成后从当前位置继续执行
  18. yield return StartCoroutine(xxx);
  19. //如果使用yield break语句,将会导致协程的执行条件不被满足,不会从当前的位置继续执行程序,而是直接从当前位置跳出函数体,回到函数的根部
  20. yield break;

协程的执行原理

协程函数的返回值时IEnumerator,它是一个迭代器,可以把它当成执行一个序列的某个节点的指针。
它提供了两个重要的接口,分别是Current(返回当前指向的元素)和MoveNext()(将指针向后移动一个单位,如果移动成功,则返回true)。

yield关键词用来声明序列中的下一个值或者是一个无意义的值。

如果使用yield return x(x是指一个具体的对象或者数值)的话,
那么MoveNext返回为true并且Current被赋值为x,如果使用yield break使得MoveNext()返回为false。
如果MoveNext函数返回为true意味着协程的执行条件被满足,则能够从当前的位置继续往下执行。否则不能从当前位置继续往下执行。

原文链接:http://www.cnblogs.com/KillerAery/p/10336388.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号