协程与迭代器

自己实现unity的协程功能_c#实现类似unity的协程-CSDN博客

前天和同事聊天,聊起来协程,聊起原理,什么迭代器,什么MoveNext

几句话带过之后就算完了,事后再次想起,发现自己已经忘了具体细节,于是也打算写成博客,供自己以后回应

一句话概括

(yield外部的)(会运行的)代码行,会被放到MoveNext()中

(写在yield return后面的类或者参数)会变成Current,Update每帧去调Current(判断是否能MoveNext),倘若返回了false,就不做任何事,否则就MoveNext()+新的初始化

具体代码+注释

查看代码

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

internal class Program
{
    static void Main(string[] args)
    {
        MyMonoBehaviour objMyMonoBehaviour = new MyMonoBehaviour();
        Console.WriteLine("Create MyMonoBehaviour" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
        objMyMonoBehaviour.StartCoroutine(CoroutineDetail());
        while (true)
        {
            objMyMonoBehaviour.Update();
            Thread.Sleep(100);
        }
    }

    static IEnumerator CoroutineDetail()
    {
        Console.WriteLine("yield return null start:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
        yield return null;
        Console.WriteLine("yield return null end:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));

        Console.WriteLine("wait 1.0 seconds start:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
        yield return new MyWaitForSeconds(1.0f);
        Console.WriteLine("wait 1.0 seconds end:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));

        Console.WriteLine("wait 2.0 seconds start:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
        yield return new MyWaitForSeconds(2.0f);
        Console.WriteLine("wait 2.0 seconds end:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
    }
}

public class MyMonoBehaviour
{
    public class RoutineInfo
    {
        //我的迭代器
        public IEnumerator routine;
        //迭代器当前需要比较的对象
        public MyYieldInstruction current;

        public bool IsCanMoveNext()
        {
            return current == null || current.IsCanMoveNext();
        }
    }

    //迭代器数据类List
    List<RoutineInfo> lstRoutine = new List<RoutineInfo>();

    public void StartCoroutine(IEnumerator routine)
    {
        //如果是空迭代器或者只能迭代一次的,直接返回
        if (routine == null || !routine.MoveNext()) return;

        //新建迭代数据类,管理该迭代器
        RoutineInfo objRoutineInfo = new RoutineInfo();
        lstRoutine.Add(objRoutineInfo);

        //初始化迭代数据类
        objRoutineInfo.routine = routine;
        SetRoutineInfo(ref objRoutineInfo);
    }

    //设置目标迭代器当前的迭代参数
    public void SetRoutineInfo(ref RoutineInfo objRoutineInfo)
    {
        //yield后面不是new了一个类嘛,存到迭代器的Current里了,要拿到类,就在这设置一下
        objRoutineInfo.current = objRoutineInfo.routine.Current as MyYieldInstruction;
    }

    public void Update()
    {
        //从后往前遍历,便于lstRoutine.RemoveAt(i)
        for (int i = lstRoutine.Count - 1; i >= 0; i--)
        {
            RoutineInfo item = lstRoutine[i];
            if (item == null) continue;

            if (!item.IsCanMoveNext()) continue;

            if (item.routine.MoveNext()) SetRoutineInfo(ref item);
            else lstRoutine.RemoveAt(i);//清除迭代完的迭代器
        }
    }
}

//抽象类+抽象方法,有其他类型的迭代器就继承这个,判断条件由自己去实现
public abstract class MyYieldInstruction
{
    public abstract bool IsCanMoveNext();
}

public class MyWaitForSeconds : MyYieldInstruction
{
    //在yield return时,记录等待时间,并用于后续的每次比较
    public float seconds;
    private DateTime beginTime;

    public MyWaitForSeconds(float seconds)
    {
        this.seconds = seconds;
        beginTime = DateTime.Now;
    }

    //MyWaitForSeconds的比较就是
    //1.在初始化时记录开始时间和等待时间
    //2.使用当前时间减去开始时间,得到时间差
    //3.使用时间差去和等待时间比较,如果时间差>等待时间,就表示可以MoveNext
    public override bool IsCanMoveNext()
    {
        TimeSpan deltaSeconds = DateTime.Now - beginTime;
        bool res = deltaSeconds.TotalSeconds > seconds;
        Console.WriteLine("MyWaitForSeconds类内部比较一次,结果为" + res);
        return res;
    }
}

输出:

 

一个比较好的数据loop办法:

posted @ 2024-03-24 20:00  被迫吃冰淇淋的小学生  阅读(36)  评论(2)    收藏  举报