C# 模拟 Unity3d 协程

chatgpt/2023/9/24 1:07:07

一、概述

由于 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.exyb.cn/news/show-5313359.html

如若内容造成侵权/违法违规/事实不符,请联系郑州代理记账网进行投诉反馈,一经查实,立即删除!

相关文章

MFC第二十四天 使用GDI对象画笔和画刷来开发控件(分页控件选择态的算法分析、使用CToolTipCtrl开发动静态提示)

文章目录 GDI对象画笔和画刷来开发控件梯形边框的按钮控件CMainDlg.hCMainDlg.cppCLadderCtrl.hCLadderCtrl.cpp 矩形边框的三态按钮控件 CToolTipCtrl开发动静态提示CMainDlg.hCMainDlg.cppCLadderCtrl.hCLadderCtrl.cpp: 实现文件 矩形边框的三态按钮控件 CToolTipCtrl开发动…

Python异步编程|ASGI 与 Django(附源码)

异步服务网关接口&#xff08;Asynchronous Server Gateway Interface&#xff0c;ASGI&#xff09;秉承WSGI统一网关接口原则&#xff0c;在异步服务、框架和应用之间提供一个标准接口&#xff0c;同时兼容WSGI。 01、ASGI ASGI是根据统一接口的思想重新设计的新标准&#xf…

搭建网站 --- 快速WordPress个人博客并内网穿透发布到互联网

文章目录 快速WordPress个人博客并内网穿透发布到互联网 快速WordPress个人博客并内网穿透发布到互联网 我们能够通过cpolar完整的搭建起一个属于自己的网站&#xff0c;并且通过cpolar建立的数据隧道&#xff0c;从而让我们存放在本地电脑上的网站&#xff0c;能够为公众互联…

STM32基础回顾

文章目录 单片机编程的原理GPIO中断EXTI外部中断定时器中断、串口中断 定时器定时器中断配置过程通用定时器输出比较功能&#xff1a;PWM波的生成定时器的输入捕获功能主从触发模式PWMI模式 定时器的编码器接口 DMA简介通信接口USART软件配置流程&#xff1a;1、仅发数据的配置…

VUE,子组件给父组件传递参数,props 自定义属性,ref

<template><div><!-- 子传父 --><!-- 通过父组件给子组件传递函数类型的props实现&#xff1a;子给父传递数据 --><AA :getAAname"getAAname"/><h1>AA&#xff1a;{{aaname}}</h1><!-- 通过父组件给子组件绑定一个自定…

玩转LaTeX(三)【数学公式(基础)、​矩阵、多行公式】

数学公式基础 导言区&#xff08;引包&#xff09; \usepackage{amsmath} %带星号的eqution 正文区 \begin{document}%数学公式初步 \section{简介} \LaTeX{}将排版内容分为文本模式和数学模式。文本模式用于普通文本排版&#xff0c;数学模式用于数学公式排版。 …

剑指offer46.把数字翻译成字符串

一开始我的想法是从后面向前面不断对100取余&#xff0c;如果这个余数大于等于10并且小于等于25&#xff0c;说明这两位既可以做一个大的字母&#xff0c;也可以做两个小的字母。所以对于前面的n-2个数字来说&#xff0c;后面的连个数字使得前面的n-2个数字的结果数翻了一倍&am…

MySQL 数据库 【增删查改(二)】

目录 一、表的设计 1、一对一 2、一对多 3、多对多 二、新增 三、查询 1、聚合查询 &#xff08;1&#xff09;聚合函数&#xff1a; &#xff08;2&#xff09; group by 子句 &#xff08;3&#xff09;having 2、联合查询 (1)内连接 (2)外连接 (3)自链接 (4)…
推荐文章