前言
在很早之前忘了是哪次面试还是什么时候,有人问我协程是什么,那时候我根本就没有这个概念,所以在网上找了一些资料,发现很多人都是讲协程和什么线程这种概念的区别,某一次我看到了某篇文章,上面详细讲解了一次协程的原理,和自己实现一个协程。那时候我也想自己去了解一下,但是因为各种各样的缘由,我在前段时间看书看到了迭代器这一章的时候,根据迭代器的定义,发现协程其实就是一个迭代器的一种实现。现在我就简单的基于协程的原理来通过迭代器实现下这个功能。
正文
迭代器
迭代器是一种设计模式,在所有讲设计模式的书上都有介绍的。它是对于遍历的一种封装,它依赖于一个叫迭代器的接口,我们通过实现这个迭代器的接口可以对各种集合的一个遍历,我们可以不用关系这个集合是数组,列表还是散列表的。
c#中的实现
- IEnumerable和IEnumerator
- IEnumerable<T>和IEnumerator<T>
上面的两个的区别是一个是基于泛型的接口还有一个是非泛型的接口
枚举源
借助c#语言的一个强大的功能,能够生成创建枚举源的方法。这些方法称为“迭代器方法”。迭代器方法用于定义请求如何在序列中生成对象。使用yield return 上下文关键字定义迭代器方法。可以在迭代器方法中使用多个离散yield return语句。迭代器有一个重要的限制:在同一个方法中不能同时使用return语句和yield return语句。
IEnumerable和IEnumerator
当我们自己实现一个迭代器的时候,我们需要创建两个类,分别继承IEnumerable和IEnumerator这两个接口,他们最重要的是IEnumerator,IEnumerable这个接口需要实现的方法是返回一个IEnumerator的对象,然后我们通过对IEnumerator进行遍历。在实现IEnumerator接口的时候我们需要实现一个是MoveNext这个方法,它是用来判断集合中是否还有下一个元素,Reset这个方法用来重置遍历,下一次从头遍历。Current这个方法是用来获取当前遍历的元素。Dispose这个方法是用来对迭代器进行回收。枚举器用来是读取集合中的数据,不能用来修改基础集合。在最开始的时候枚举数定位在集合中第一个元素的最前面,所以在reset的时候把这个枚举数定义为-1,当我们调用MoveNext的时候会判断当前的枚举数(++curindex)是否在集合的最后一位,如果已经在最后一位了,那就会返回一个false,然后current的值不变,如果不在最后一位,那就把current的值修改到下一个元素。因为是通过枚举数来确定是否在集合的最后一位,所以当我们对集合做出了修改,就会导致枚举器的行为是不确定的。在这里我发现了一个事情,就是默认的IEnumerator的current的返回值是一个object的类型,这个会不会就是人们常说的用foreach来遍历集合会有很高的消耗呢?毕竟如果是一个int这样的值类型会有一次拆装箱的操作。如果说被优化了,那优化点就是值类型的泛型他们单独实现。通过我查看了下他们的源码发现真的是这样的,他们对list的IEnumerator自己改成了一个Enumerator的一个结构(struct)内部实现的Current是一个T的泛型,所以这里就不会出现我看到的拆装箱的操作。
yield
- yield return <expression>
- yield break
使用yield return 语句可一次返回一个元素。
迭代器方法运行到yield return语句时,会返回一个expression,并保留当前在代码中的位置。下次调用迭代器函数的时候,将从该位置重新开始执行。
使用yield break来终止迭代器。
返回yield或IIEnumerable的迭代器的IEnumerator类型为object。所以当我们yield return返回一个int这样的值类型的时候会进行一次装箱操作。
yield break
是用来跳出迭代器的。
协程
IEnumerator test2 = test();
while (test2.MoveNext())
{
Console.WriteLine(test2.Current);
}
IEnumerator test()
{
Console.WriteLine(DateTime.Now);
yield return new test1();
Console.WriteLine(DateTime.Now);
}
public class test1
{
public test1()
{
System.Threading.Thread.Sleep(2000);
}
}
看到上面的代码有没一种很熟悉的感觉,这个是我实现的一个延时执行下一行代码的协程,在实现的时候,我一直在想,其实我们可以把expression当成是一个同步阻塞方法,当这个方法执行结束了之后,我们的迭代器再执行下面的方法。
结束语
万变不离其宗,掌握了原理其实很多东西都是很简单的。当前我就吃了研究不深的亏,很多东西都是用就完事了。现在回想起来,知识这个东西还是不能拖的,趁着热情还在,仔细研究透。