C# 模拟 Unity3d 协程

一、概述

由于 Unity3d 在开发游戏时使用的是单线程,为了给开发者提供异步相关的操作,于是开发者在 Unity3d 中加入了协程的概念,协程在 Unity3d 中用的非常多,也有些大佬觉得这玩意儿不好用,还不如用一些插件。

在 C# 没有协程对应的接口,但是我们可以封装一个,我百度查了一下,实现这些功能的代码大致差不多,为了实现在一个方法里多次等待,都使用了 IEnumerator 的相关概念,如下代码:

IEnumerator Test()
{
    Console.WriteLine("开始");
    yield return new WaitForSeconds(2);
    Console.WriteLine("结束1");
    yield return new WaitForSeconds(2);
    Console.WriteLine("结束2");

    Console.WriteLine("完成");
}

但是,在 C# 原生的开发中,其实根本用不到这些,使用 Thread + Thread.Sleep(等待时间) 很容易实现这个功能,另外,使用异步加 await 关键字也可以实现这些功能,唯一的区别是,C# 自带的异步等方式,取消等待的执行,稍微麻烦了一些,也更复杂,另外,多线程用的不好,也容易出现一些突发的 bug,一旦代码量大了,不是那么好解决。

其实,在 Winform 等开发中,定时器使用的多了,也是很麻烦的:

1.关闭程序之前,假设不关闭定时器,有时候程序都关闭不了,一直处于卡死的状态。

2.定时器用的多了,程序运行的时间长了,很容易闪退。

3.定时器代码没有统一管理,比较混乱,时间久了,自己都不记得用了几个定时器了。

所以,在项目中使用协程,也未必不是一个好的解决办法,不过,前提是要好好的测试。

关于异步相关的教程,可以参考帖子:

C# async / await 任务超时处理_task启动后 c#处理超时如何退出_熊思宇的博客-CSDN博客

关于 IEnumerator 相关的教程,可以参考帖子:

C# IEnumerator 用法_c#ienumerator_熊思宇的博客-CSDN博客

二、实现功能

新建一个类库 CoroutineLibrary,以 dll 的形式更方便其他项目的调用。

添加一个类 Coroutine

using System.Collections;

namespace CoroutineLibrary
{
    public class Coroutine
    {
        private IEnumerator routine;

        //返回 false 当前的协程将会被从链表中移除
        public bool Next()
        {
            if (routine == null)
                return false;

            IWait wait = routine.Current as IWait;

            //如果当前延时还没有结束,会一直重复的调用 Tick
            bool timeIsOver = true;
            if (wait != null)
                timeIsOver = wait.Tick();

            if (!timeIsOver)
                return true;
            else
                //如果当前的延时已经结束,那么就移动到下一个迭代
                //如果成功移动到下一个迭代,则返回true,否则返回false
                return routine.MoveNext();
        }

        public Coroutine(IEnumerator routines)
        {
            routine = routines;
        }
    }

    /// 
    /// 等待接口
    /// 
    internal interface IWait
    {
        /// 
        /// 每帧检测是否等待结束
        /// 
        /// 
        bool Tick();
    }
}

Coroutine 类的主要作用是检查当前的迭代,是否到了指定的时间,比如间隔是一秒,Next 方法会不停的被调用,判断是否要进入下一个迭代。

添加一个类 WaitForSeconds

namespace CoroutineLibrary
{
    public class WaitForSeconds : IWait
    {
        private float waitTime = 0;

        bool IWait.Tick()
        {
            waitTime -= 0.1f;
            return waitTime <= 0;
        }

        public WaitForSeconds(float time)
        {
            waitTime = time;
        }
    }
}

waitTime -= 0.1f 是根据 CoroutineLibrary 类的定时器每秒的执行次数来决定的,因为定时器我写的是100毫秒执行一次,那么1秒就会执行10次,每次减等于 0.1,10次刚好是1。

添加一个类 CoroutineLibrary

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

namespace CoroutineLibrary
{
    public class CoroutineCSharp
    {
        /// 
        /// 存储所有协程对象
        /// 
        private static LinkedList CoroutineList = new LinkedList();
        /// 
        /// 需要停止的协程
        /// 
        private static Coroutine StopCoroutine = null;
        /// 
        /// 定时器
        /// 
        private static System.Timers.Timer Timer1 = null;

        /// 
        /// 初始化
        /// 
        private static void Init()
        {
            Timer1 = new System.Timers.Timer();
            Timer1.Interval = 100;
            Timer1.AutoReset = true;
            Timer1.Elapsed += Timer1_Elapsed;
            Timer1.Enabled = true;
        }

        private static void Timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            UpdateCoroutine();
        }


        /// 
        /// 开启一个协程
        /// 
        /// 
        /// 
        public static Coroutine Start(IEnumerator ie)
        {
            if(Timer1 == null) Init();

            var c = new Coroutine(ie);
            CoroutineList.AddLast(c);
            return c;
        }

        /// 
        /// 停止一个协程
        /// 
        /// 
        public static void Stop(Coroutine coroutine)
        {
            StopCoroutine = coroutine;
        }

        private static void UpdateCoroutine()
        {
            var node = CoroutineList.First;
            while (node != null)
            {
                bool ret = false;
                var cor = node.Value;
                if (cor != null)
                {
                    bool toStop = StopCoroutine == cor;
                    if (!toStop)
                        ret = cor.Next();
                }
                if (!ret)
                {
                    CoroutineList.Remove(node);
                }
                node = node.Next;
            }
        }


        private CoroutineCSharp() { }

        ~CoroutineCSharp()
        {
            Timer1.Enabled = false;
        }
    }
}

在添加任务时,会把迭代器存储到一个链表中,然后由定时器反复刷新,判断这些迭代器是否到了规定的时间,是否需要停止执行。

关于模拟协程的所有代码就这些了,下面对一些基本的功能进行测试。

三、测试

新建一个 winform 项目,将上面的 类库 CoroutineLibrary 添加进来,并添加两个按钮。

Form1 代码如下:

using CoroutineLibrary;
using System;
using System.Collections;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 模拟协程
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        Coroutine coroutine = null;

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void button1_Click(object sender, EventArgs e)
        {
            coroutine = CoroutineCSharp.Start(test1());
        }

        private void button2_Click(object sender, EventArgs e)
        {
            CoroutineCSharp.Stop(coroutine);
        }

        IEnumerator test1()
        {
            while (true)
            {
                yield return new WaitForSeconds(1);
                Console.WriteLine("定时器1");
            }
        }

        IEnumerator test2()
        {
            Console.WriteLine("2开始");
            yield return new WaitForSeconds(2);
            Console.WriteLine("2结束1");
            yield return new WaitForSeconds(2);
            Console.WriteLine("2结束2");

            Console.WriteLine("2完成");
        }
    }
}

运行:

C# 模拟 Unity3d 协程_第1张图片

第一个方法 test1,test1 方法是一个 while 循环,所以,每隔一秒就会输出一次,第二个方法 test2 会分为几次输出,执行完成后,这个方法也不会再执行了。

下面测试多个类同时执行协程,并取消其中的两个协程,看看效果。

先添加一个类 Test1

public class Test1
{
    private string Name { get; set; }
    public bool Switchs { get; set; }

    private Coroutine Coroutines { get; set; }

    public void Start()
    {
        Coroutines = CoroutineCSharp.Start(StartYield());
    }

    public void Stop()
    {
        CoroutineCSharp.Stop(Coroutines);
    }

    IEnumerator StartYield()
    {
        while (true)
        {
            yield return new WaitForSeconds(1);
            Console.WriteLine("定时器,Name:{0}", Name);

            if (Switchs)
                break;
        }
    }

    public Test1(string name)
    {
        this.Name = name;
    }
}

Form1 代码做一些改动

using CoroutineLibrary;
using System;
using System.Collections;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 模拟协程
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        Test1 tests1 = new Test1("a1");
        Test1 tests2 = new Test1("a2");
        Test1 tests3 = new Test1("a3");
        Test1 tests4 = new Test1("a4");

        private void button5_Click(object sender, EventArgs e)
        {
            tests1.Start();
            tests2.Start();
            tests3.Start();
            tests4.Start();
        }

        private void button6_Click(object sender, EventArgs e)
        {
            tests2.Switchs = true;
            tests3.Switchs = true;
        }
    }
}

开启多个任务

C# 模拟 Unity3d 协程_第2张图片

取消其中的两个任务

C# 模拟 Unity3d 协程_第3张图片

结束

如果这个帖子对你有所帮助,欢迎 关注 、点赞 、留言

end

你可能感兴趣的:(c#)