unity3d的协程1——初步理解背后的迭代器(代码片段)

渐澄 渐澄     2023-01-06     729

关键词:

Unity协程的概念:

        协程存在于许多编程语言中,Unity3D在调用我们编写的C#脚本时,会将它们统一放在一条主线程当中调度,所有的游戏对象、游戏组件都在这条主线程中。其他的线程并不能访问这些数据,所以对于我们所写的所有脚本来说,Unity是单线程的。

        既然Unity3D不能多线程,那肯定需要一种机制来模拟多线程,来解决这种问题。这个机制便是协程。

要理解什么是协程,先让我们看看迭代器:

迭代器:

        让我们先来看看下面的代码

List<int> arr = new List<int>() 0, 1, 2, 3, 4 ;
foreach (int i in arr)
    Debug.log(i);

不知道各位有没有想过,这个foreach到底做了什么,arr又是因为什么,能够遍历这个数组中的所有元素?

让我们查看List<>的元数据:

public class List<T> : ICollection<T>, IEnumerable<T>, IEnumerable, IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection, IList

List这个泛型类继承了很多的接口,有一个貌似与我们要探讨的问题有关—— IEnumerable和 IEnumerable<T>。

让我们继续深入,看看它们的代码:

IEnumerable:

using System.Runtime.InteropServices;

namespace System.Collections

    public interface IEnumerable
    
        IEnumerator GetEnumerator();
    

我们可以看到,继承了这个接口的类必须实现一个方法——返回一个IEnumerator的方法,这个IEnumerator又是什么呢?继续深入下去:

这是IEnumerator的代码

namespace System.Collections

    public interface IEnumerator
    
        object Current  get; 

        bool MoveNext();
        void Reset();
    

IEnumerator又是一个接口,函数返回了一个接口,事情有点开始绕了。这个接口中有一个Current、一个返回bool型的MoveNext方法、还有个Reset()方法

此处我查阅了微软官方的文档,有对IEnumerator接口的详细解释。 

IEnumerator官方文档解释

 看了这段文字,可能还是有很多人云里雾里,不知所云,没关系,让我们把上面的例子详细理解一下:

1.首先,微软定义了一个Person类:

// Simple business object.
public class Person

    public Person(string fName, string lName)
    
        this.firstName = fName;
        this.lastName = lName;
    

    public string firstName;
    public string lastName;

这个类有姓、名两个字符串字段和一个构造函数为它们初始化值。

2.接着,定义了一个People类,并让他继承IEnumerator接口

// Collection of Person objects. This class
// implements IEnumerable so that it can be used
// with ForEach syntax.
public class People : IEnumerable

    private Person[] _people;
    public People(Person[] pArray)
    
        _people = new Person[pArray.Length];

        for (int i = 0; i < pArray.Length; i++)
        
            _people[i] = pArray[i];
        
    

    // Implementation for the GetEnumerator method.
    IEnumerator IEnumerable.GetEnumerator()
    
        return (IEnumerator)GetEnumerator();
    

    public PeopleEnum GetEnumerator()
    
        return new PeopleEnum(_people);
    

        People这是Person的容器,内部一个Person类型的数组_people,实现IEnumerator接口是为了让他能够被Foreach语句调用,构造函数接收一个Person类型的数组,并将其复制进_people中,至此都没什么好留意的。

        需要注意的是,这里出现了两个GetEnumerator方法,上面的是实现接口之用,这个类中还是可以有一个与接口中方法同名的方法成员。

        下面它实现了IEnumerator接口中的GetEnumerator()方法,返回GetEnumerator,并将它强制转换为IEnumerator接口。下面的GetEnumerator方法返回的则是一个PeopleEnum对象,这个PeopleEnum又是什么呢,让我们继续往下看:

4.PeopleEnum类,实现了IEnumerator接口,离真相越来越近了

// When you implement IEnumerable, you must also implement IEnumerator.
public class PeopleEnum : IEnumerator

    public Person[] _people;

    // Enumerators are positioned before the first element
    // until the first MoveNext() call.
    int position = -1;

    public PeopleEnum(Person[] list)
    
        _people = list;
    

    public bool MoveNext()
    
        position++;
        return (position < _people.Length);
    

    public void Reset()
    
        position = -1;
    

    object IEnumerator.Current
    
        get
        
            return Current;
        
    

    public Person Current
    
        get
        
            try
            
                return _people[position];
            
            catch (IndexOutOfRangeException)
            
                throw new InvalidOperationException();
            
        
    

这个类还和People类一样,有一个Person类型的数组,和一个为它初始化的构造函数,下面实现了IEnumerator——MoveNext,Reset,Current两个方法和一个属性,结合上面官方文档的注释和博主一步一步的调试,终于算是搞懂了这是怎么一回事。

PeopleEnum中有一个标记位置的参数position,默认为-1,调用MoveNext()方法时,会将这个位置值+1,然后判断是否到了数组的尽头,并将判断的结果返回,如果没有到数组末尾,返回true,表示可以继续下一轮,一旦返回false则停止遍历。

Reset()方法便是直接将position重置为-1;

Current为只读,返回_people数组中的第position位元素

5.这是Main()函数中的内容:

    static void Main()
    
        Person[] peopleArray = new Person[3]
        
            new Person("John", "Smith"),
            new Person("Jim", "Johnson"),
            new Person("Sue", "Rabon"),
        ;

        People peopleList = new People(peopleArray);
        foreach (Person p in peopleList)
            Console.WriteLine(p.firstName + " " + p.lastName);
    

        首先是初始化一个Person数组并将他赋值给People类中,接下来进入了foreach语句,程序首先是通过peopleList进入了People类的GetEnumerator方法中:

    IEnumerator IEnumerable.GetEnumerator()
    
        return (IEnumerator)GetEnumerator();
    

然后进入自己实现的GetEnumerator方法中:

    public PeopleEnum GetEnumerator()
    
        return new PeopleEnum(_people);
    

将自身的_people数组传入并返回,因为PeopleEnum本身实现了IEnumerator接口,所以将它的返回时转换成IEnumerator类型也是合理的,这就返回了一个_people的枚举器。结合上面对该接口中三个成员的描述与下面的单步调试过程,程序的运行逻辑便清晰明了了:

得到了枚举器之后,程序会首先进入MoveNext()方法,position++,为0,返回ture,表示可以继续遍历,之后访问Current属性,返回_people数组的第0位元素“John Smith”将它赋值给p,然后把p打印出来。

然后再进入MoveNext方法,position++,为1,返回true,可以继续遍历,Current返回第1位元素,打印。

再次进入MoveNext方法,position++,为2,返回true,可以继续遍历,Current返回第2位元素,打印。

再次进入MoveNext方法,position++,为3,这时返回false,此时退出foreach语句。

如果我们将MoveNext中的返回值改为false,那么控制台不会打印任何信息,进一步验证了我的想法。

总结一下思路:能被foreach语句遍历的类必须继承 IEnumerable,表示这个是一个可以被枚举的类,继承该接口的类必须实现一个GetEnumerator方法,该方法返回一个枚举器IEnumerator,foreach凭借其实现的MoveNext,Current便可以遍历我们想要遍历的内容啦。

大致的结构便是这样的:

         最后的最后,其实继承Enumerator的并不一定要是一个额外的类,完全可以是一个自己的结构体成员,就像List的元数据那样:

public List<T>.Enumerator GetEnumerator();
public struct Enumerator : IEnumerator<T>, IEnumerator, IDisposable

    public T Current  get; 
    public void Dispose();
    public bool MoveNext();

python协程初步---一个迭代器的实现(代码片段)

一般认为迭代器就是实现了两个方法__iter__和__next__先创建这样一个类fromcollectionsimportIterablefromcollectionsimportIteratorclassclassiterable(object):def__iter__(self):passdef__next__(self):passclassmycoach(object):def__init 查看详情

网络编程-协程-1迭代器(代码片段)

知识点:什么叫迭代器?说起for遍历大家应该很熟悉,foriinxxx,in后面的对象是一个可迭代的对象,可迭代的对象不一定是迭代器,如列表,字典,字符串等这些都是可迭代对象,迭代器是调用了对象内部的__iter__方法和__next__方... 查看详情

Unity3d - For 循环中的协程

】Unity3d-For循环中的协程【英文标题】:Unity3d-CoroutineinForLoop【发布时间】:2014-03-0519:30:13【问题描述】:我正在使用Unity3D,特别是C#,我正在尝试编写一个For循环,在1个循环中实例化多个游戏对象,但我想在下一场游戏之前稍... 查看详情

day10:kotlin的协程已经安卓网络技术初步(代码片段)

网络技术一、利用Http访问网络二、解析网络上常见的两种数据1.xml格式2.json格式三、Retrofit的使用四:Kotlin:协程一、利用Http访问网络GET代表希望从服务器那里获取数据POST则代表向服务器提交数据网络请求一般在子线程... 查看详情

F# 中的协程

...】:2013-11-1714:21:46【问题描述】:我刚刚开始使用F#使用Unity3D,我注意到coroutines在书籍和教程中被大量使用,作为解决各种问题的巧妙解决方案。我一直在试图弄清楚F#是否具有等效的内置结构,或者是否至少可以以某种方式模... 查看详情

unity从各个点理解协程

...f0c;如果需要大量的计算,将计算放到一个随时间进行的协程来处理,能分散计算压力协程的坏处协程本质是迭代器,且是基于Unity生命周期的,大量开启协程会引起GC如果同时激活的协程较多,就可能会出现多... 查看详情

python高级语法——协程/迭代器/生成器——学习心得笔记

Python高级语法——协程——学习心得笔记1.迭代器可迭代(Iterable):直接作用于for循环的变量迭代器(Iterator):不但可以用于for循环,还可以被next调用list是典型的可迭代对象,但不是迭代器isinstance判断是否可迭代或者迭代器iter转... 查看详情

深入理解koltin协程:序列构建器(代码片段)

...中,如Python或JavaScript,你可以找到一些有限形式的协程结构:async函数(也称为async/await)生成器函数(用于产生后续值的函数)我们已经看到了如何在Kotlin中使用异步操作,但这将会在之后的协程... 查看详情

python迭代器和生成器的个人理解,再讲一讲协程(代码片段)

  在认识yield的时候,网上很多文章都是说这个是个生成器,但是我并不知道这个是用来做什么的,所以概念很快就忘记了,后面读了几个文章以后感觉茅塞顿开。我就接介绍一下。  有一篇文章提到,可以把yield看成是生... 查看详情

深入理解kotlin协程协程作用域启动模式调度器异常和取消使用篇(代码片段)

...,因此对于协程的创建,框架中提供了不同目的的协程构造器。这两组 API的差异在千Receiver的有无。Receiver通常用千约束和扩展协程体,剩下的部分就是作为协程体的 suspend函数和作为协程完成后回调的 completion。 ... 查看详情

笔试面试题实现

...迭代器生成器以及他们之间的区别6.什么是协程,Python中的协程是如何实现的7.什么是装饰器,请使用装饰器实现singletion。8.请使用Python实现快速排序9.简述s 查看详情

php中for和foreach背后发生了什么和关于迭代器的理解(代码片段)

for和foreach简单描述php中的for处理方式类似于c语言,只要expr2表达式为真,则循环会一直继续下去:for(expr1;expr2;expr3)statementphp中的foreach主要是为了遍历数组和对象:foreach开始执行的时候,数组内部的指针会自动指向第一个单元,每进行... 查看详情

yield方式转移执行权的协程之间不是调用者与被调用者的关系,而是彼此对称平等的

 zh.wikipedia.org/wiki/协程与子例程一样,协程也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。协程源自Simula和Modula-2语言,但也有其他语言支持。协程更适合于用来实现彼此熟... 查看详情

yield学习续:yieldreturn迭代块在unity3d中的应用——协程

必读好文推荐:Unity协程(Coroutine)原理深入剖析Unity协程(Coroutine)原理深入剖析再续 上面的文章说得太透彻,所以这里就记一下自己的学习笔记了。首先要说明的是,协程并不是线程,协程是运行在主线程中的,是和主... 查看详情

进程线程协程的初步理解

进程进程是操作系统进行资源分配的基本单位,简单理解就是正在运行程序的实例。进程的用户空间是相互独立,系统空间是共享的,它们之间进行通信(即数据交换)需要通过系统空间来完成。线程线程是程序调度的... 查看详情

python中的协程与asyncio原理(代码片段)

Python协程与asyncio原理直接看Python代码,下面有详细的注释:#研究asyncio与协程的原理,python版本3.8#以下仅从代码执行与调试过程来理解协程,并不一定与协程的真正实现一致#Python用保存函数的栈帧来恢复暂停点... 查看详情

python中的协程与asyncio原理(代码片段)

Python协程与asyncio原理直接看Python代码,下面有详细的注释:#研究asyncio与协程的原理,python版本3.8#以下仅从代码执行与调试过程来理解协程,并不一定与协程的真正实现一致#Python用保存函数的栈帧来恢复暂停点... 查看详情

线程和协程有啥区别呢?

...理。1.协程,即协作式程序,其思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。协程实际上是在一个线程中,只不过每个协程对CUP进行分时,协程可以访问和使用unity的所有方... 查看详情