【转】Unity中的协同程序-使用Promise进行封装(一)

原文:http://gad.qq.com/program/translateview/7170767

译者:陈敬凤(nunu)    审校:王磊(未来的未来)

每个Unity的开发者应该都对协同程序非常的熟悉。对于很多Unity的开发者而言,协同程序就是用来编写大量异步和延时任务的一种方法。如果你不在乎速度的话,有非常非常多的特殊方法可以在任何所需的时间暂停和恢复执行。在实践中,它们可以营造一种并发函数的幻觉 (虽然他们与线程无关!)。然而,协同程序会有一些问题,许多程序员在使用协同程序的时候会偶然发现。让我们仔细看看这些问题。

协同程序的内部机制

那么在Unity协同程序的内部到底发生了什么?Unity协同程序到底是如何工作的?我们没有直接访问Unity源代码的权利,但是我们可以从手册中收集证据,并且通过C#的知识我们可以或多或少的假设它们到底是如何工作的。让我们尽量精简这个示例代码:


1

2

3

4

5

6

7

8

9

StartCoroutine(TestCoroutine());

//…

IEnumerator TestCoroutine()

{

    Debug.Log("Hello there!");

    yield return new WaitForSeconds(2);

    Debug.Log("Hello from future!");

}

你不需要什么特殊的才能,就能很容易的看出这段代码将在终端部分打印出“Hello there!”并在2秒之后打印出” Hello from future! “。但它是如何做到这一点的?要理解协同程序必须首先看下函数的签名——更准确地说,是函数的返回类型。IEnumerator作为一种对集合进行迭代的方法。它控制着从一个对象的执行转移到序列中下一个对象的执行。

为了做到这一点,它声明了两个非常重要的成员变量:一个是Current(当前)属性,它会引用枚举器(或者可以说是游标)目前正在访问的元素,另外一个是MoveNext()函数,它在移动到下一个元素的同时会计算新的Current(当前)值。它也有一个Reset()函数,这个函数会负责将枚举器设置到它的初始位置,但是我们跳过这一部分。

现在由于IEnumerator只是一个接口,并不显式地指定当前类型(除了是一个对象以外我们一无所知)。我们可以做任何我们想要的事情来计算下一个对象。MoveNext()函数只会做这项工作,并且我们已经访问到序列的最后一个元素的时候会返回fasle。

迭代器模块

如果是在一个纯C#的环境,我们可以轻松地在一个特化的迭代器模块里面“实现”这个接口。在实践中,c#编译器会将迭代器模块转换成状态机。这是一些会返回IEnumerator类型并且使用yield return语句来返回值的函数(是的,有可能是多个值)。

调用会使MoveNext()函数只是简单的返回true,并且无论你是从哪返回都将当前的位置保存到Current变量里面。如果你想要停止枚举器的话,你可以简单的调用yield break,这将确保MoveNext()函数返回false并终止整个序列(可以把它想象成在一个循环中进行break)。下面是这么做的一个例子:


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

29

30

31

32

33

34

35

36

37

38

39

40

41

using System;

using System.Collections.Generic;

 

class TestIEnumerator

{

    static readonly string inEnumerator = "####";

 

    static IEnumerator GetTexts()

    {

        Console.WriteLine(inEnumerator + "First line of GetTexts()" + inEnumerator);

 

        Console.WriteLine(inEnumerator + "Just before the first yield" + inEnumerator);

        yield return "First returned text";

        Console.WriteLine(inEnumerator + "Just after the first yield" + inEnumerator);

 

        Console.WriteLine(inEnumerator + "Just before the second yield" + inEnumerator);

        int b = 2;

        int a = 5 + b;

        yield return "Second returned text - " + a;

        Console.WriteLine(inEnumerator + "Just after the second yield" + inEnumerator);

    }

 

    static void Main()

    {

        Console.WriteLine("Calling GetTexts()");

        IEnumerator iterator = GetTexts();

        Console.WriteLine("Calling MoveNext()...\n");

        bool returnedValue = iterator.MoveNext();

        Console.WriteLine("\nReturned value = {0}; Current = {1}", returnedValue, iterator.Current);

 

        Console.WriteLine("Calling MoveNext() again...\n");

        returnedValue = iterator.MoveNext();

        Console.WriteLine("\nReturned value = {0}; Current = {1}", returnedValue, iterator.Current);

 

        Console.WriteLine("Calling MoveNext() again...\n");

        returnedValue = iterator.MoveNext();

        Console.WriteLine("\nReturned value = {0} - stopping", returnedValue);

 

        Console.ReadKey();

    }

}

在你自己的电脑上编译这个程序,你会得到以下输出:

下面的例子使用了通用接口IEnumerator,但是正如前面所描述的那样,你可以使用常规的IEnumerator接口,在这个接口里面你的Current变量可以是任意类型。这也正是Unity对于协同程序的要求。

所以,请记住这一点,我们对于Unity到底与协同程序做了什么开始有了一个比较清晰的认识。StartCoroutine函数将协同程序添加到某个容器之中。Unity遍历StartCoroutine中执行的每一个协同程序并执行这些协同程序的MoveNext()函数,而这些函数会来继续执行他们之前中断的工作。正如上面的例子所显示的那样,它会在yield return语句之间评估表达式的值并返回一个值。如果它返回的是false的话,那么显然是在告诉Unity中止这个协同程序(它只是刚刚完成而已)。如果它返回的是true的话,,它会检查当前的属性(记住,这是一个非泛型接口!)并且看下是否有熟悉的类型。还记得第一个例子之中的WaitForSeconds()吗?Unity看到这个函数然后暂停了这个协同程序两秒钟。事实上,它实际上是从YieldInstruction基类型继承而来,你还有以下Unity可以识别的类型:

1) WaitForEndForFrame:在所有的摄像机和GUI都被渲染之后,会在这一帧的结尾来继续这个协同程序。

2) WaitForFixedUpdate:会等到下一个以固定帧速率更新的函数。

3) Coroutine类型自身,这是一个你可以用在之后协同程序的信息。

4) CustomYieldInstruction:这是引入用来写你自己的自定义yield语句-仅仅需要继承这个类然后覆盖keepWaiting属性。

所以焦点在哪里?

不用说,通过这些简单的类型你可以很容易的编写一些基于时间的游戏逻辑以及各种异步事件。你可以通过短短几行代码不使用任何肮脏的计时器在几秒钟后展示一个UI通知。不仅如此,你甚至可以返回UnityWebRequest.Send()函数并暂停你的函数直到你得到一个HTTP响应。你可能会问“这有什么问题吗?”

首先,你不能轻易地从协同程序返回一个值。函数签名需要返回IEnumerator来保持跟踪应该在何时何恢复你的方法。其次,在协同程序级别没有异常处理。你不能在try。。。catch块里面使用yield语句。基本上你要试着在协同程序里面的每一个non-yield语句里面尝试使用try。。。catch块。但是如果有多个表达式通过多个yield语句隔离开来怎么办?或者你想要一个接一个的将协同程序放入堆栈。但如果最上面的协同程序遇得到了一个运行时异常的时候该如何沿着管线沟通下面的协同程序?

幸运的是,这个问题可以有一个解决方案。在接下来的部分我们会看到Promise,它们最初是出现在Javascript之中。请在 这 里 阅读这部分的内容。

时间: 2024-10-21 09:14:36

【转】Unity中的协同程序-使用Promise进行封装(一)的相关文章

Unity 中的协同程序

今天咱就说说,协同程序coroutine.(这文章是在网吧敲的,没有unity,但是所有结论都被跑过,不管你信得过我还是信不过我,都要自己跑一下看看,同时欢迎纠错)先说说啥是协程:协同程序是一个非常让人作呕的东西,它的表现形式非常像线程,对线程有过接触的朋友可能更理解我这句话的意思,你没接触过线程,那么理解它会有一些难度.但是它不存在线程安全问题,可以放心使用.这不是J哥信口雌黄空口白牙跟这猜的,事实是这样的:在操作系统层面,也就是更古老的大神们,觉得“并发”是一个很时髦的东西,很好使,于是他们

Lua中的协同程序

[前言] 协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈.局部变量和指令指针,同时又与其它协同程序共享全局变量和其它大部分东西.从概念上讲,线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行.就是说,一个具有多个协同程序的程序在任意时刻只能运行一个协同程序,并且正在运行的协同程序只会在其显式地要求挂起时,它的执行才会暂停. [协同程序基础] Lua将所有关于协同程序的函数放置在一个名为"coroutine"的table中

Lua中的协同程序 coroutine

Lua中的协程和多线程很相似,每一个协程有自己的堆栈,自己的局部变量,可以通过yield-resume实现在协程间的切换.不同之处是:Lua协程是非抢占式的多线程,必须手动在不同的协程间切换,且同一时刻只能有一个协程在运行.并且Lua中的协程无法在外部将其停止,而且有可能导致程序阻塞. 协同程序(Coroutine): 三个状态:suspended(挂起,协同刚创建完成时或者yield之后).running(运行).dead(函数走完后的状态,这时候不能再重新resume). coroutine

Lua中的协同程序 coroutine(转)

Lua中的协程和多线程很相似,每一个协程有自己的堆栈,自己的局部变量,可以通过yield-resume实现在协程间的切换.不同之处是:Lua协程是非抢占式的多线程,必须手动在不同的协程间切换,且同一时刻只能有一个协程在运行.并且Lua中的协程无法在外部将其停止,而且有可能导致程序阻塞. 协同程序(Coroutine): 三个状态:suspended(挂起,协同刚创建完成时或者yield之后).running(运行).dead(函数走完后的状态,这时候不能再重新resume). coroutine

Unity中自定义应用程序打开Assets目录下指定类型的文件

在Unity使用VS2017打开unityShader文件时总提示错误: 我也一直没找啥原因,我直接把之前工具拿过来了,shader文件直接使用vsCode打开,其他类型的文件也可这样处理,如:pdf,txt等文件均可 具体代码如下: 1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEditor; 4 using UnityEngine; 5 6 public class SetAssets

unity中开启和关闭协同程序

协同程序这个好处大大的,用过的人都说好 转载出处:http://wg2009perfect.blog.163.com/blog/static/127997663201211111222126/ 一.什么是协同程序 协同程序,即在主程序运行时同时开启另一段逻辑处理,来协同当前程序的执行.换句话说,开启协同程序就是开启一个线程. 二.协同程序的开启与终止 在Unity3D中,使用MonoBehaviour.StartCoroutine方法即可开启一个协同程序,也就是说该方法必须在MonoBehavi

转载 Unity3D协同程序(Coroutine)

摘要下: 1. coroutine, 中文翻译"协程".这个概念可能有点冷门,不过百度之,说是一种很古老的编程模型了,以前的操作系统里进程调度里用到过,现在操作系统的进程调度都是根据 时间片和优先级来进行轮换,以前是要程序自己来释放cpu的控制权,一直不释放一直也就占用着cpu,这种要求程序自己来进行调度的编程模型应该就叫"协 程"了. 协程和线程差不多,线程的调度是由操作系统完成的,协程把这项任务交给了程序员自己实现,当然也就可以提高灵活性,另外协程的开销比线程要

Lua语言基础汇总(7) -- 协同程序

前言 协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈.局部变量和指令指针,同时又与其它协同程序共享全局变量和其它大部分东西.从概念上讲,线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行.就是说,一个具有多个协同程序的程序在任意时刻只能运行一个协同程序,并且正在运行的协同程序只会在其显式地要求挂起时,它的执行才会暂停. 协同程序基础 Lua将所有关于协同程序的函数放置在一个名为“coroutine”的table中.函数create

《Lua程序设计》9.1 协同程序基础 学习笔记

协同程序(coroutine)与线程(thread)差不多,也就是一条执行序列,拥有自己独立的栈.局部变量和指令指针,同时又与其他协同程序共享全局变量和其他大部分东西.从概念上讲线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作地运行.就是说,一个具有多个协同程序的程序在任意时刻只能运行一个协同程序,并且正在运行的协同程序只会在其显式地要求挂起(suspend)时,它的执行才会暂停. Lua将所有关于协同程序的函数放置在一个名为“coroutine