一、概述
由于 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;//如果当前延时还没有结束,会一直重复的调用 Tickbool timeIsOver = true;if (wait != null)timeIsOver = wait.Tick();if (!timeIsOver)return true;else//如果当前的延时已经结束,那么就移动到下一个迭代//如果成功移动到下一个迭代,则返回true,否则返回falsereturn routine.MoveNext();}public Coroutine(IEnumerator routines){routine = routines;}}/// <summary>/// 等待接口/// </summary>internal interface IWait{/// <summary>/// 每帧检测是否等待结束/// </summary>/// <returns></returns>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{/// <summary>/// 存储所有协程对象/// </summary>private static LinkedList<Coroutine> CoroutineList = new LinkedList<Coroutine>();/// <summary>/// 需要停止的协程/// </summary>private static Coroutine StopCoroutine = null;/// <summary>/// 定时器/// </summary>private static System.Timers.Timer Timer1 = null;/// <summary>/// 初始化/// </summary>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();}/// <summary>/// 开启一个协程/// </summary>/// <param name="ie"></param>/// <returns></returns>public static Coroutine Start(IEnumerator ie){if(Timer1 == null) Init();var c = new Coroutine(ie);CoroutineList.AddLast(c);return c;}/// <summary>/// 停止一个协程/// </summary>/// <param name="coroutine"></param>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完成");}}
}
运行:
第一个方法 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;}}
}
开启多个任务
取消其中的两个任务
结束
如果这个帖子对你有所帮助,欢迎 关注 、点赞 、留言
end