迭代器异步等待与Unity协程

枚举器和可枚举接口

C# 基础类库中集成了迭代器模式,定义了枚举器和可枚举接口,可枚举接口中定义了一个函数,返回枚举器接口,枚举器中定义了两个函数和一个属性,同时FCL中也定义了两种接口的泛型,利用这两种接口,我们可以实现任意一种集合的迭代器和可迭代具体类。网上有很多类似的教程,这里就不展开叙述。

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

public interface IEnumerator
{
    object Current { get; }

    bool MoveNext();

    void Reset();
}

Yield 关键字

手动实现迭代器非常繁琐,C# 中引入了Yield关键字,通过这个关键字,编译器可以帮助我们自动实现迭代器,例如现在有一个整型数组,想要用迭代器方式来访问这个类数组中的元素,那么我们可以用Yield关键字来快速实现。迭代器方式是指我不需要管集合类内部元素是用什么方式组织或者存储的,但是可以依次遍历并读取其中的元素。

public class IntIterable:IEnumerable
{
    public int[] nums = new int[] { 1, 2, 3, 4, 5, 6, 7 };

    public IEnumerator GetEnumerator()
    {
        for(int i=0;i<nums.Length;i++)
        {
            yield return nums[i];
        }
    }
}

枚举器的遍历方式

遍历方式有两种,手动通过枚举器接口中的属性和函数遍历,或者用foreach语法糖自动遍历。

//手动遍历
public class IterableTest
{
    public static void Main()
    {
        IEnumerable iterable = new IntIterable();
        IEnumerator iterator = iterable.GetEnumerator();
        while(true)
        {
            bool result = iterator.MoveNext();
            if(!result)
            {
                break;
            }
            Console.WriteLine(iterator.Current);
        }
    }
}

//自动遍历
public class IterableTest
{
    public static void Main()
    {
        IEnumerable iterable = new IntIterable();
        foreach(int i in iterable)
        {
            Console.WriteLine(i);
        }
    }
}

Yield关键字前后执行顺序

在《C# in depth》一书中给出了一个非常好的例子,可以帮助我们理解Yield的手动遍历以及Yield关键字前后的执行顺序,在这里我们将程序及相应的结果先给出来。

class IteratorWorkflow
{
    static readonly string Padding = new string(' ', 30);

    static IEnumerable<int> GetEnumerable()
    {
        Console.WriteLine("{0}Start of GetEnumerator()", Padding);

        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine("{0}About to yield {1}", Padding, i);
            yield return i;
            Console.WriteLine("{0}After yield", Padding);
        }

        Console.WriteLine("{0}Yielding final value", Padding);
        yield return -1;

        Console.WriteLine("{0}End of GetEnumerator()", Padding);
    }

    public static void Main()
    {
        IEnumerable<int> iterable = GetEnumerable();
        IEnumerator<int> iterator = iterable.GetEnumerator();

        Console.WriteLine("Starting to iterate");
        while (true)
        {
            Console.WriteLine("Calling MoveNext()...");
            bool result = iterator.MoveNext();
            Console.WriteLine("... MoveNext result={0}", result);
            if (!result)
            {
                break;
            }
            Console.WriteLine("Fetching Current...");
            Console.WriteLine("... Current result={0}", iterator.Current);
        }
    }
}
Starting to iterate
Calling MoveNext()...
                              Start of GetEnumerator()
                              About to yield 0
... MoveNext result=True
Fetching Current...
... Current result=0
Calling MoveNext()...
                              After yield
                              About to yield 1
... MoveNext result=True
Fetching Current...
... Current result=1
Calling MoveNext()...
                              After yield
                              About to yield 2
... MoveNext result=True
Fetching Current...
... Current result=2
Calling MoveNext()...
                              After yield
                              Yielding final value
... MoveNext result=True
Fetching Current...
... Current result=-1
Calling MoveNext()...
                              End of GetEnumerator()
... MoveNext result=False

从运行结果可以看到,每次执行MoveNext函数后,可迭代类中会运行到Yield关键字,如果类中有该关键字,MoveNext函数返回True,没有返回False;下次再执行MoveNext函数,又会执行可迭代类中的代码,直到再次运行到Yield关键字。最终可迭代类中没有Yield关键字,MoveNext函数返回False,迭代器结束。

迭代器与异步

考虑上面代码Main函数中while循环和可迭代类GetEnumerable中的代码执行逻辑,可以实现异步等待效果。例如我需要在while循环5次后输出“Hello Iterator”,但是期间while循环不要被阻塞,一直可以输出“Hello World”。接下来我们用代码来实现这种异步等待。

class IteratorWorkflow
{
    static IEnumerable<int> GetEnumerable()
    {
        for (int i = 0; i < 4; i++)
        {
            yield return i;
        }
        Console.WriteLine("Hello Iterator");
    }

    public static void Main()
    {
        IEnumerable<int> iterable = GetEnumerable();
        IEnumerator<int> iterator = iterable.GetEnumerator();


        while (true)
        {
            Console.WriteLine("Hello World");
            bool result = iterator.MoveNext();
        }
    }
}
Hello World
Hello World
Hello World
Hello World
Hello World
Hello Iterator
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World

迭代器异步等待

如果我们需要实现一定时间段的异步等待操作,又该怎么用迭代器来实现呢?
对于这样一定时长的异步等待操作,可以在迭代器中开启一个循环,并设置一个时间判断,当判断不通过时,迭代器永远都在循环中自我迭代,不会执行后续操作,而当判断通过时,迭代器跳出循环,执行一定时长的异步等待操作。

class IteratorWorkflow
    {
        static DateTime current;

        static IEnumerator<int> GetEnumerable()
        {
            while(true)
            {
                if (DateTime.Now.Subtract(current).TotalMilliseconds > 1000)
                    break;
                yield return 1;
            }

            Console.WriteLine("Hello Iterator");
        }

        public static void Main()
        {
            IEnumerator<int> iterator = GetEnumerable();
            current = DateTime.Now;

            while (true)
            {
                Console.WriteLine("Hello World");
                bool result = iterator.MoveNext();
            }
        }
    }

Unity协程

Unity协程也是用迭代器来实现的,不过提供了更好的封装效果和调用接口。在Unity协程中,可以选择多种方式的等待,具体的方式可以参考这篇博客。类似上面的做法,我们可以更加简单地使用Unity协程来实现一定时间的异步等待效果。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.UI;
 
public class Wait : MonoBehaviour 
{
	void Start () 
	{
        StartCoroutine(waitCreate(0.5f));
	}
 	void Update()
 	{
 		print("Hello World");
 	}
    private IEnumerator waitCreate(float seconds)
    {
        yield return new WaitForSeconds(seconds);
        print("Hello Iterator");        
    }
}

Unity 迭代器异步

如果Start方法返回一个迭代器,那么也可以实现异步操作,比如下面的程序,Update中会一直输出no,但是5s后会输出yes。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnumeratorTest : MonoBehaviour {

	IEnumerator Start () {
        yield return new WaitForSeconds(5);
        print("yes");
	}
	
	void Update () {
        print("no");
	}
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值