一、概述
由于 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;
}
}
/// <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