C# 模拟 Unity3d 协程

news2024/11/15 17:28:43

一、概述

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

若依vue -【 44 ~ 53 】

44 服务监控讲解 1 需求 显示CPU、内存、服务器信息、Java虚拟机信息、磁盘状态的信息 2 前端 RuoYi-Vue\ruoyi-ui\src\views\monitor\server\index.vue <script> import { getServer } from "/api/monitor/server";export default {name: "Server&quo…

Linux 查看磁盘空间

1 查看当前目录的总大小 &#xff1a;du -sh ps&#xff1a;du(disk usage) 2 查看某个目录的总大小&#xff1a;du -sh 目录名 3 查找出/目录下占用空间最大的前10个文件或者文件夹&#xff1a;sudo du -a / | sort -n -r | head -n 10 4 查看磁盘信息:df -h

旧项目导入Eclipse时文件夹看起来乱七八糟,无从下手的解决办法(无main或webapp等文件夹)

首先&#xff0c;如果没有main或java/resource/webapp等文件夹&#xff0c;那就自己在src下面创建一个&#xff0c;只要对应关系与我下图左边红框一致即可&#xff0c;创建完之后java文件移到java文件夹下&#xff0c;资源文件例如.properties、老项目的数据源定义.INI文件、日…

Vue2 第十二节 Vue组件化编程(一)

1.模块与组件&#xff0c;模块化与组件化概念 2. 非单文件组件 3. 组件编写注意事项 4. 组件的嵌套 一. 模块与组件&#xff0c;模块化与组件化 传统方式编写存在的问题 &#xff08;1&#xff09;依赖关系混乱&#xff0c;不好维护 &#xff08;2&#xff09;代码的复用…

基于SWAT-MODFLOW地表水与地下水耦合

目录 第一讲 模型原理剖析 第二讲 QGIS高级操作 第三讲 QSWATMOD操作 第四讲 数据制备 第五讲 基于CUP的率定验证 第六讲 结果分析 第七讲 控制措施效果模拟 第八讲 土地变化情景模拟 第九讲 气候变化情景模拟 更多推荐 耦合模型被应用到很多科学和工程领域来改善模型…

谷歌: 安卓补丁漏洞让 N-days 与 0-days 同样危险

近日&#xff0c;谷歌发布了年度零日漏洞报告&#xff0c;展示了 2022 年的野外漏洞统计数据&#xff0c;并强调了 Android 平台中长期存在的问题&#xff0c;该问题在很长一段时间内提高了已披露漏洞的价值和使用。 更具体地说&#xff0c;谷歌的报告强调了安卓系统中的 &quo…

ChatGLM-6B VS 昆仑万维天工对比

下面进行昆仑万维天工与ChatGLM-6B内测结果进行对比&#xff0c;其中ChatGLM-6B的结果来自https://github.com/THUDM/ChatGLM-6B&#xff0c;假设ChatGLM-6B的结果是可靠的&#xff0c;那么为了公平&#xff0c;昆仑万维天工&#xff08;https://tiangong.kunlun.com/interlocu…

OSG3.6.5 + VS2017前期准备及编译

OSG3.6.5 VS2017前期准备及编译 1、前期准备 1.1、osg稳定版本源码 Stable releases (openscenegraph.com) 1.2、osg依赖项 Dependencies (openscenegraph.com) 1.3、osg测试及演示数据 Data Resources (openscenegraph.com) 1.4、安装doxygen和Graphviz&#xff08;用…

组件化、跨平台…未来前端框架将如何演进?

前端框架在过去几年间取得了显著的进步和演进。前端框架也将继续不断地演化&#xff0c;以满足日益复杂的业务需求和用户体验要求。从全球web发展角度看&#xff0c;框架竞争已经从第一阶段的前端框架之争&#xff08;比如Vue、React、Angular等&#xff09;&#xff0c;过渡到…

SolidWorks二次开发---简单的连接solidworks

创建一个.net Framework的应用&#xff0c;正常4.0以上就可以了。 打开nuget包管理 在里面搜索paine 在版中选择对应的solidworks年份开头的&#xff0c;进行安装。 安装完之后 : 同时选中下面两个dll,把嵌入操作类型改为false 然后在按钮的单击事件中输入: Connect.Crea…

使用powershell找回丢失的RDCManage密码

内网的一台服务器上的装机默认用户密码忘记了&#xff0c;但是好在别的电脑上使用RDCMan&#xff08;Remote Desktop Connection Manager&#xff09;连接过这台服务器&#xff0c;并且保存了密码。于是经过一番折腾&#xff0c;最后把密码找回来了&#xff1a; 最后成功的powe…

【ChatGPT】ChatGPT是如何训练得到的?

前言 ChatGPT是一种基于语言模型的聊天机器人&#xff0c;它使用了GPT&#xff08;Generative Pre-trained Transformer&#xff09;的深度学习架构来生成与用户的对话。GPT是一种使用Transformer编码器和解码器的预训练模型&#xff0c;它已被广泛用于生成自然语言文本的各种…

抓紧收藏,Selenium无法定位元素的几种解决方案

01、frame/iframe表单嵌套 WebDriver只能在一个页面上对元素识别与定位&#xff0c;对于frame/iframe表单内嵌的页面元素无法直接定位。 解决方法&#xff1a; driver.switch_to.frame(id/name/obj) switch_to.frame()默认可以直接取表单的id或name属性。如果没有可用的id和…

基于量子同态的安全多方量子求和加密

摘要安全多方计算在经典密码学中一直扮演着重要的角色。量子同态加密(QHE)可以在不解密的情况下对加密数据进行计算。目前&#xff0c;大多数协议使用半诚实的第三方(TP)来保护参与者的秘密。我们使用量子同态加密方案代替TP来保护各方的隐私。在量子同态加密的基础上&#xff…

Day49 算法记录|动态规划16 (子序列)

这里写目录标题 583. 两个字符串的删除操作72. 编辑距离总结 583. 两个字符串的删除操作 这道题求得的最小步数&#xff0c;是这道题的变种 M i n ( 步数&#xff09; s t r 1. l e n g t h s t r 2. l e n g t h − 2 ∗ ( M a x ( 公共字符串长度&#xff09; ) Min(步数…

子域名收集工具OneForAll的安装与使用-Win

子域名收集工具OneForAll的安装与使用-Win OneForAll是一款功能强大的子域名收集工具 GitHub地址&#xff1a;https://github.com/shmilylty/OneForAll Gitee地址&#xff1a;https://gitee.com/shmilylty/OneForAll 安装 1、python环境准备 OneForAll基于Python 3.6.0开发和…

Linux系统安装Nginx(保姆级教程)

目录 一、环境准备 二、开始安装 2.1、解压Nginx文件 2.2、编译安装 2.3、启动Nginx 2.4、安装成系统服务&#xff08;脚本&#xff09; 2.5、常见问题 本机如何访问虚拟机中的Nginx&#xff1f; 编译安装的过程中出现如下几种警告错误 一、环境准备 系统&#xff1a…

教程|如何用 Docker/K8s 快速部署 StarRocks 集群

云原生是一种现代化的软件开发和部署方法论。相较于传统的应用开发和部署方式&#xff0c;云原生带来了显著的优势&#xff0c;包括弹性伸缩、应用程序可移植性、高可靠性和自动化部署与管理等方面&#xff0c;从而极大地提升了成本效益和开发效率。 StarRocks 从 3.0 版本开始…

wms三代电子标签操作指导

一、服务器使用 V1.4基站已经内置服务程序&#xff0c;无需搭建服务&#xff1b;可跳至第1.4部分 1、服务器搭建 安装mysql5.7, 创建db_wms数据库并导入原始数据库文件 安装jdk1.8, 配置java环境变量 下载tomca8.0, 部署wms.war到tomcat, 并启动tomcat 2、下载资源 Wind…

dflow工作流使用1——架构和基本概念

对于容器技术、工作流等概念完全不懂的情况下理解dflow的工作方式会很吃力&#xff0c;这里记录一下个人理解。 dflow涉及的基本概念 工作流的概念很好理解&#xff0c;即某个项目可以分为多个步骤&#xff0c;每个步骤可以实现独立运行&#xff0c;只保留输入输出接口&#x…