如何理解协程?

2020-06-02 10:04发布

6条回答
男孩无衣
2楼 · 2020-06-02 10:09

----------先说说线程和进程, 如果看不懂直接略过看第二部分---------

从字面上看,协程与线程、进程较为相似,对于拥有其它开发语言基础的同学容易造成困扰,以至于第一印象把协程理解成线程。

线程是操作系统能够进行运算调度的最小单位,分配算力、执行调度以线程为单位。一条线程指就是一个单一顺序的控制流。

进程是正在运行的程序的实例,是线程集合的载体,同时也是操作系统分配资源的基本单位。


线程和进程的关系,打个比方,一个应用程序就是一个进程。进程启动后,好比建立了一个工厂,线程是这个工厂中的“流水线”。一个工厂内可以有多个流水线,这些流水线可以并发生产,一条流水线出问题不会影响其它流水线。所以,进程可以有多个线程,但至少有一个主线程。

线程间可以并发执行,并共享进程资源(都使用进程的空间)。线程间有独立的栈区,但共享使用进程的堆区。

Unity中不支持多线程开发,但是却有并发的需求,使用协程来模拟多线程开发,即可并发(模拟),又可控制同步、防止阻塞。

协程是Unity机制,不具备系统普遍性,只能在MonoBehaviour下启动。 协程和线程的定义、启动流程类似,但其内部实现原理与线程不同。


------------------这里开始介绍协程-----------------------------

Unity的协程系统是基于C#的一个简单而强大的接口,简单讲就是可以把一个方法拆分成多次执行的一种接口。协程通过yiled return返回一个迭代器,记录程序执行的位置,之后进入阻塞状态,直到满足唤醒条件后,才会继续向下执行。启动多个协程后,多个协程之间不会真正的并发,而通过快速轮询模拟并发效果。

Unity在整个生命周期中构建了一个托管代码执行队列,通过生命周期管理器往这个队列中添加执行方法的委托,然后启动一个托管线程,这个线程中不断的循环获取队列中的方法委托并执行。

当开启一个迭代器(IEnumerator)后,协同程序便开始执行,在执行到yield return 之前和一般的程序没有任何区别,遇到yield return 之后立即返回,并将之后的代码挂起。之后判断yield return后边的条件是否满足,如果满足,向下继续执行,否则继续等待。

IEnumerator methodName(Object parameter1,Object parameter2,...){

    // to do something

    yield return Yield Instruction/other/null;

    // to do something else

}


协同程序又称协程,即在主程序运行时同时开启另一段逻辑处理,来协同当前程序的执行。

协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。通俗点说:程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

注意协程不是线程,也不是异步执行的。协程和 MonoBehaviour 的 Update函数一样也是在MainThread中执行的。

协同程序可以和主程序并行运行,和多线程有点类似。

Mantra
4楼 · 2020-06-03 09:02

在unity的游戏开发中,对于异步操作,有一个避免不了的操作: 协程,以前一直理解的懵懵懂懂,最近认真充电了一下,通过前辈的文章大体理解了一下,在这儿抛砖引玉写一些个人理解。当然首先给出几篇写的非常精彩优秀的文章,最好认真拜读一下:

王迅:Coroutine从入门到劝退zhuanlan.zhihu.com  

Unity3d中协程的原理,你要的yield return new xxx的真正理解之道blog.csdn.net  

Unity协程(Coroutine)原理深入剖析dsqiu.iteye.com

好了,接下来就从一个小白的视角开始理解协程。

 

二、常见使用协程的示例

经常,我们会利用monobehaviour的startcoroutine来开启一个协程,这是我们在使用unity中最常见的直观理解。在这个协程中执行一些异步操作,比如下载文件,加载文件等,在完成这些操作后,执行我们的回调。 举例说明:

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
public static void Download(System.Action finishCB)
{
      string url = "https: xxxx";
      StartCoroutine(DownloadFile(url));
}
 
private static IEnumerator DownloadFile(string url)
{
     UnityWebRequest request = UnityWebRequest.Get(url);
     request.timeout = 10;
     yield return request.SendWebRequest();
     if(request.error != null)     
     {
                Debug.LogErrorFormat("加载出错: {0}, url is: {1}", request.error, url);
                request.Dispose();
                yield break;
      }
      
      if(request.isDone)
      {
            string path = "xxxxx";
            File.WriteAllBytes(path, request.downloadHandler.data);
            request.Dispose();
            yiled break;
      }
}

这个例子中,用到了几个关键词: IEnumerator/yield return xxx/ yield break/StartCoroutine, 那么我们从这几个关键词入手,去理解这样的一个下载操作具体实现。

1、关键词 IEnumerator

这个关键词不是在Unity中特有,unity也是来自c#,所以找一个c#的例子来理解比较合适。首先看看IEnumerator的定义:

1
2
3
4
5
6
public interface IEnumerator
{
     bool MoveNext();
     void Reset();
     Object Current{get;}
}

从定义可以理解,一个迭代器,三个基本的操作:Current/MoveNext/Reset, 这儿简单说一下其操作的过程。在常见的集合中,我们使用foreach这样的枚举操作的时候,最开始,枚举数被定为在集合的第一个元素前面,Reset操作就是将枚举数返回到此位置。

迭代器在执行迭代的时候,首先会执行一个 MoveNext, 如果返回true,说明下一个位置有对象,然后此时将Current设置为下一个对象,这时候的Current就指向了下一个对象。当然c#是如何将这个IEnumrator编译成一个对象示例来执行,下面会讲解到。

2、关键词 Yield

c#中的yield关键词,后面有两种基本的表达式:

yield return 
yiled break

yield break就是跳出协程的操作,一般用在报错或者需要退出协程的地方。

yield return是用的比较多的表达式,具体的expresion可以以下几个常见的示例:

WWW : 常见的web操作,在每帧末调用,会检查isDone/isError,如果true,则 call MoveNext
WaitForSeconds: 检测间隔时间是否到了,返回true, 则call MoveNext
null: 直接 call MoveNext
WaitForEndOfFrame: 在渲染之后调用, call MoveNext


蜗牛
5楼 · 2020-06-03 09:11

协程语法怎样理解:

1.协程,顾名思义,就是“协同程序”,用来实现协作。 2.比如在游戏中需要等待1秒后执行一个操作。我们不能让游戏主线程卡死等1秒,而是开启一个协程进行协作,协程同样是由主线程来驱动(所以说它不是线程),会在每一帧去检测它是否已经达到了完成的条件。比如条件是1秒后执行一个操作,那么在1秒后主线程对它检测时。条件满足,于是就执行先前设定好的相关操作。

为什么要有协程?

  1. Uniy核心逻辑是单线程,为了开发便利,统一生命周期管理,不需要考虑锁的问题。

  2. 一些导步操作非常耗时,比如资源加载,如果让用户去使用多线程开发,整个开发逻辑会非常复杂。而通过协程封装起来,方便用户使用。并且对于一些耗废时间的操作,Unity会在引擎底层通过多线程去完成,而协程则通过不断的访问这个操作的状态判断加载是否完成。

启动协程的方法:

  1. StartCoroutine(IEnumerator routine)

  2. StartCoroutine(string routine)或StartCoroutine(string routine,object value) 通过传入函数名字的字符串启动协程的性能开销要更高,但这种方式启动的协程可以通过StopCoroutine(string methondname)来终止,这种方式还有一个限制就是只能传递一个参数。

协程终止的方法

  1. StopCoroutine(string methondname)只能终止通过字符串名字启动的协程

  2. StopCoroutine(Coroutine coroutine)

  3. StopAllCoroutines()终止所有协程

  4. 在协程内部终止可以使用

协程的一些特性

  1. 在一个协程中可以启动另一个协程

  2. 一个协程可以暂停另一个协程

  3. 两个协程也可以同时并行工作

  4. 协程在执行过程中可以通过yield在任何时间点暂停,yeild返回的值决定了协程何时恢复执行

  5. 在多个游戏帧循环中进行操作时,协程表现十分出色,几乎没有额外性能开销

  6. StartCoroutine()函数总是会立即返回,不管你yeild返回的值是什么

  7. 多个协程结束时的顺序是无法保证与其启动顺序一致的,即使他们是在同一帧当中结束

关于yeild返回的值

  1. 可以是null,数字 ,字符串,布尔值甚至表达式,函数,嵌套协程。

  2. 当return是一个函数调用,赋值表达式,嵌套协程时,会直接调用这个函数或表达式。

  3. 协程一般情况下是在Update调用完成之后运行,在yeild后面的条件满足之前,协程会一直挂起在那里。

  4. 不同的yeild返回类型对应了不同的条件:

    1. null,数字,字符串:会在下一帧所有Update()函数运行之后继续运行

    2. WaitForSeconds:延迟指定的时间之后,在所有的Update()函数运行之后继续运行

    3. WaitForFixedUpdate:在所有FixedUpdate() 函数运行之后继续运行。(注意这里不是指下一帧,因为FixedUpdate()的帧率和Update()是不一样的)

    4. WWWW:当WWW资源加载成功以后继续运行。(其它的异步资源加载函数也是一样)


  1. StartCoroutine:运行完成嵌套的协程以后再继续运行。

如果还要继续深入研究协程的实现,可以查看《CLR via C#》以及《深入理解C#》这两本书中迭代器实现原理,Unity的底层就是通过它来实现的协程。

Unity里,一个典型的协程如下:

using UnityEngine;using System.Collections;public class ExampleClass : MonoBehaviour {
    void Start() {
        StartCoroutine(FuncA());
    }

    IEnumerator FuncA() {
        Debug.Log("Log1");
        yield return new WaitForSeconds(1.0f);
        Debug.Log("Log2");
        yield return StartCoroutine(FuncB());
        Debug.Log("Log3");
    }

    IEnumerator FuncB() {
        Debug.Log("Log4");
        yield return new WaitForSeconds(2.0f);
        Debug.Log("Log5");
    }}

运行输出结果为:

Log1
(等待1s)  
Log2  
Log4  
(等待2s)  
Log5  
Log3


花轮同学
6楼 · 2020-06-03 18:20

我用一个非技术的角度给你说一下协程

举例:你在和甲打电话。甲问你要乙的电话号码【我们把这个事件叫做A】。此时你在保持与甲通话的情况下去通讯录找乙的电话。然后将电话号码告诉甲【我们把这个事件叫做B】 A和B两个事件的状态就是协程。事件A再等待B事件完成,然后A事件才会继续。整个的流程就是协程。

Transform
7楼 · 2020-06-04 01:39

协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。它不会影响主线程运行。它不是被操作系统内核所管理的,而是完全由程序所控制,也就是在用户态执行。这样带来的好处是性能大幅度的提升,因为不会像线程切换那样消耗资源。协程不是进程也不是线程,而是一个特殊的函数,这个函数可以在某个地方挂起,并且可以重新在挂起处外继续运行。所以说,协程与进程、线程相比并不是一个维度的概念。

相关问题推荐

  • 回答 6

    第一步:对着Assets点击右键,选择ExportPackage第二步:选择场景文件以及和场景相关的资源或者素材,然后点击Export第三步:给导出的资源取名,并且选择要保存的位置即可

  • 回答 87
    已采纳

    玩游戏玩的很好,说明你对于游戏里面的规则、剧情设置还是比较了解的,对于从事游戏相关岗位来说也是优势之一。但是学习游戏开发还是要对游戏开发的工作内容做进一步的了解,游戏开发涉及代码较多,可以通过进一步的了解,判断自己是否适合学习这个方向,另外...

  • 回答 18

    个人觉得如果有一定的技术基础的话还是可以考虑自学,如果零基础的话可能会有些难度

  • 回答 11

    游戏开发入门不难。后期发展需要你有丰富的奇思妙想。游戏开发肯定是培训好,自学学得不系统,并且不易发现自身薄弱之处。游戏开发的学习时长还是要看你自己对知识与技术的掌握能力,一般来说,游戏开发的学习时长大约在五个月左右。...

  • 回答 10

    问题还是出在粒子的sorting fudge。在unity的2d模式下,游戏本身的背景相当于是sorting fudge的0,当你把粒子的sorting fudge设为0以上的时候,粒子就都会被背景盖住。所以在3d模式下给alpha正值来给add垫底的话,到了2d模式下就会通通不显示。所以遇上这样的...

  • 回答 17

    虚幻4引擎,你会看到和平精英加载页面左下角有这个图标。

  • 回答 8
    已采纳

    转载知乎上的两位答友的回答,各有道理。作者:风小锐链接:https://www.zhihu.com/question/322249959/answer/675883379来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。未来Unity有可能出现3A大作吗? 有可能。基于Unity已...

  • 回答 11

    在Assets文件夹里面.点击右键Create/Material即可以创建材质球

  • 回答 23

    可以让模型师直接作出这样的形状,如果用纯Unity制作,就要用基本游戏对象拼接了,包括楼梯,城堡,都可以拼接出来。正常情况不会这样做,因为不够精美,都是建模师来实现,毕竟Unity不属于专业的建模软件,侧重于实现功能。...

  • 回答 18

    粒子系统由粒子发射器、粒子动画器和粒子渲染器三部分组成,主要用于游戏场景中一些特殊效果,如水、烟火等等

  • 回答 18

    首先,Python开发游戏非常尴尬,原因是没有好用的游戏开发库。Python开发游戏仅推荐PyGame,PyGame是对多年以前很流行的游戏开发库——SDL的封装。但是说实话功能太简陋了,做个动画都得考虑刷新的问题。楼主要做简单小游戏,只需要画一两周熟悉Unity引擎,然...

  • 回答 9

    1.标记水体碰撞的位置2.计算水波的传递 通过波动公式,3D或者2D 波动公式都行3.水面顶点采样波动传递结果计算结果做顶点Y轴偏移

  • 回答 15

    Unity3D中两种阴影的实现传统的ShadowMapShadowMap说起来十分简单,把摄像机和光源的位置重叠,那么场景中该光源的阴影区域就是那些摄像机看不到的地方,主要应用在前向渲染路径中。具体实现分以下几个步骤:如果有平行光开启了阴影,Unity就会为该光源计算它...

  • 回答 18

    Doozy UI是Unity UI视图层的框架,本身使用的还是UGUI的组件,但提供了一套强大的UI管理功能,可以很方便的实现一些炫酷效果,方便的UI系统管理与事件传递机制。

  • 回答 12

    Unity3d更好,因为U3D占有的市场更大,目前cocos大都是用来开发棋牌游戏的,在这方面它有着巨大的优势。而Unity3d既可以用来开发大型3D游戏,也可以用来开发vr游戏、vr应用,这是比较不错的,未来有着巨大的前景。另外ue4也是个不错的选择,近年来用ue4开发的...

  • 回答 11

    当Unity 需要做热更新的时候(2013年开始),而普通的C#又做不到的时候,而对于游戏行业来说Lua脚本热更新已经是很成熟的方案,自然Lua 热更新就成为了Unity热更新的首选。

没有解决我的问题,去提问