協程與迭代器

被迫吃冰淇淋的小学生發表於2024-03-24

自己實現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;
    }
}

輸出:

協程與迭代器

相關文章