1.4 Unity协程

一、先说接口

接口是不能实例化的,想实例化接口,只能实例化继承了接口的类。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InterfaceTest {
    interface IMyInterface {
        void AMethod();
    }
    class Test : IMyInterface {
        public void AMethod() {
            Console.WriteLine("Extends from IMyInterface");
        }
    }

    class Program {
        static void Main(string[] args) {
            IMyInterface a = new Test();
            a.AMethod();
            Console.ReadKey();
        }
    }
}

如果实例化的接口引用 继承自同一个接口的不同的类,这个实例就可以调用这些类实现的该接口的所有方法。

二、C# yield

1.官方解释

如果你在语句中使用 yield 关键字,则意味着它在其中出现的方法、运算符或 get 访问器是迭代器。 通过使用 yield 定义迭代器,可在实现自定义集合类型的 IEnumerable 和 IEnumerator 模式时无需其他显式类(保留枚举状态的类,有关示例,请参阅 IEnumerator<T>)。

2.实际上yield是一个语法糖

既然yield是在C#中的一个语法糖,那么就说明yield是对一种复杂行为的简化,就是将一段代码简化为一种简单的形式,方便我们程序员使用。

那么yield到底是对什么行为的简化。我们首先来看一下yield的使用场景。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
         
            foreach (int i in Power(2, 8, ""))
            {
                Console.Write("{0} ", i);
            }
            Console.ReadKey();
        }


        public static IEnumerable<int> Power(int number, int exponent, string s)
        {
            int result = 1;

            for (int i = 0; i < exponent; i++)
            {
                result = result * number;
                yield return result;
            }
            yield return 3;
            yield return 4;
            yield return 5;
        }

    }
}

定义了一个返回类型是IEnumerable<int>的静态方法,方法名叫Power,如果按照平时做法,应该

return一个IEnumerablel<int>类型的参数。

public static IEnumerable<int> Power(int number, int exponent, string s)
        {
            int result = 1;
            //接口不能实例化,我们这儿new一个实现了IEnumerable接口的List类
            //接口是不能实例化的!想实例化接口,只能实例化继承了接口的类
            IEnumerable<int> example = new List<int>();
            for (int i = 0; i < exponent; i++)
            {
                result = result * number;
                (example as List<int>).Add(result);
            }
            return example;
        }

但是因为要new一个List,或者任何实现了IEnumerable接口的类型,这样也太麻烦了吧!要知道IEnumerable是一个常用的返回类型。每次使用都要new一个LIst,或者其他实现了该接口的类型。与其使用其他类型,不如我们自己定制一个实现了IEnumerable接口专门用来返回IEnumerable类型的类型。我们自己定制也很麻烦。所以微软帮我们定制好了。这个类是什么,那就是yield关键字这个语法糖。

3.示例1 自定义迭代 

public IEnumerable<int> Integers()
{
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 16777216;
}

对Integers()的第一次调用返回1。第二次调用返回2,并且yield return 1;不再执行。

4.示例2 使用中的特殊情况

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            //这儿调用了方法。
            var test = Power(2, 8, "");
            Console.WriteLine("Begin to iterate the collection.");
            //Display powers of 2 up to the exponent of 8:
            foreach (int i in Power(2, 8, ""))
            {
                Console.Write("{0} ", i);
            }
            Console.ReadKey();
        }
        public static IEnumerable<int> Power(int number, int exponent, string s)
        {
            int result = 1;
            if (string.IsNullOrEmpty(s))
            {
                //throw new Exception("这是一个异常");
                Console.WriteLine("Begin to invoke GetItems() method");
            }

            for (int i = 0; i < exponent; i++)
            {
                result = result * number;
                yield return result;
            }
            yield return 3;
            yield return 4;
            yield return 5;
        }
    }
}

按照我们的理解当我们 var test = Power(2, 8, "");的时候确实调用了Power方法。此时应该程序打印Console.WriteLine("Begin to invoke GetItems() method");然后继续执行 Console.WriteLine("Begin to iterate the collection.");方法。所以打印顺序应该是

Begin to invoke GetItems() method

Begin to iterate the collection.

但是我们运行的时候却发现

看了反编译代码,发现我们的打印方法并没有出现在Power方法中,而是被封装进了实现枚举接口的类方法  private bool MoveNext()中。所以方法不会立即被执行,而是在我们使用数据的时候被执行。如果对此机制不了解,就容易出现另外一些意想不到的问题。例如在Power方法中添加一些验证程序,如果不符合条件就抛出一个异常。这样的异常检查不会被执行。只有我们使用数据的时候才会执行。这样就失去了检查数据的意义。

另外使用yield还有一些注意事项:

你不能在具有以下特点的方法中包含 yield return 或 yield break 语句:

  • 匿名方法。 有关详细信息,请参阅匿名方法(C# 编程指南)。
  • 包含不安全的块的方法。 有关详细信息,请参阅unsafe(C# 参考)。

异常处理

不能将 yield return 语句置于 try-catch 块中。 可将 yield return 语句置于 try-finally 语句的 try 块中。

yield break 语句可以位于 try 块或 catch 块,但不能位于 finally 块。

如果 foreach 主体(在迭代器方法之外)引发异常,则将执行迭代器方法中的 finally 块。

三、协程!

1.线程和协程的概念

一个应用程序一般对应一个进程,一个进程一般有一个主线程,还有若干个辅助线程,线程之间平行运行,在线程里面可以开启协程,让程序在特定的时间内运行。

协程与协程之间是并行执行,与主线程也是并行执行,同一时间只能执行一个协程。

线程可以多线程,但是在Unity中只能在主线程中获取物体的组件、方法和游戏对象,但是协程中是可以正常获取游戏对象信息的!

在主线程中执行一个对资源消耗很大的操作时,就会影响游戏帧率,所以可以利用协程来做!

2.协程原理

协程是通过迭代器来实现的,通过关键字IEnumerator来定义一个方法

  • IEnumerator是一个实现迭代器功能的接口
  • IEnumerable是在IEnumerator基础上的一个封装接口,有一个GetEnumerator()方法返回IEnumerator

线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程由操作系统调度。

协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。

3.使用协程

1.定义协程

通过IEnumerator定义一个协程,通过yield return来定义返回值

    IEnumerator Test(string str)
    {
        Debug.Log("协程被启动了!"+ str);
        yield return null;
    }

2.启动协程

1.StartCoroutine(string methodName):通过协程的方法名(字符串形式)启动

2.StartCoroutine(string methodName, object values):带参数的通过方法名(字符串形式)启动

3.StartCoroutine(IEnumerator routine):通过调用方法的形式启动

    void Start()
    {
        //通过调用方法名来启动协程
        StartCoroutine("Test1");

        //通过调用方法名来启动协程
        StartCoroutine("Test2", "Hello World");

        //直接调用方法启动协程
        StartCoroutine(Test1());
        StartCoroutine(Test2("Hello World"));
    }

    IEnumerator Test1()
    {
        Debug.Log("协程被启动了!");
        yield return null;
    }
    IEnumerator Test2(string str)
    {
        Debug.Log("协程被启动了!" + str);
        yield return null;
    }

3.停止协程

StopCoroutine和StopAllCoroutines两种方式都可以停止协程

1.StopCoroutine(string methodName):通过协程的方法名(字符串形式)关闭协程

2.StopCoroutine(IEnumerator routine):通过调用方法来关闭协程

3.StopCoroutine(Coroutine routine):通过指定的协程来关闭

4.StopAllCoroutines停止所有该脚本中启动的协程

    void StopTest()
    {
        //第一种方式:通过调用方法的形式来关闭协程
        StopCoroutine(Test1());

        //第二种方式:通过方法名(字符串)来关闭协程
        StopCoroutine("Test1");

        //第三种方式:通过指定的协程来关闭
        Coroutine a = StartCoroutine(Test1());
        StopCoroutine(a);

        //关闭该脚本中启动的所有协程!
        StopAllCoroutines();
    }

注意:使用哪一种开启协程的方法,就要用哪种方式来关闭!

4.yield Return

  • yield return null 暂停协程等待下一帧继续执行
  • yield return 0或其他数字 暂停协程等待下一帧继续执行
  • yield return new WairForSeconds(时间) 等待规定时间后继续执行
  • yield return new WaitForFixedupdate() 等到下一个固定帧数更更新
  • yield return new WaitForEndOfFrame() 等到所有相机画面被渲染完毕后更新
  • yield return StartCoroutine("协程方法名") 开启一个协和程(嵌套协程)
    void Start()
    {
        Debug.Log("a");
    }

    void Update()
    {
        Debug.Log("b");
        StartCoroutine(Test1());
        Debug.Log("d");
    }

    IEnumerator Test1()
    {
        Debug.Log("c");
        yield return null;
        Debug.Log("e");
    }

打印:第一帧a,b,c,d,第二帧b,c,d,e

代码遇到yield return之后会暂停一帧,跳出协程,等到下一帧再根据yield的生命周期位置来进行打印。

5.计时器案例演示

IEnumerator Test()
{
    Debug.Log("开始协程了");
    yield return new WaitForSeconds(3);//等待三秒执行下方代码块
    Debug.Log("三秒时间到了,执行此处代码!");
}

可以利用yield return new WairForSeconds(时间) 执行许多需要延迟的功能。

还有需要异步加载时都可以用到协程,比如AB包的异步加载、Resources资源的异步加载、场景的异步加载等。

4.底层原理

  • 协程是通过迭代器来实现功能的,通过关键字IEnumerator来定义一个迭代方法。
  • StartCoroutine 接受到的是一个 IEnumerator ,这是个接口,并且是枚举器或迭代器的意思。
  • yield 是 C#的一个关键字,也是一个语法糖,背后的原理会生成一个类,并且也是一个枚举器,而且不同于 return,yield 可以出现多次。
  • yield 实际上就是返回一次结果,因为我们要一次一次枚举一个值出来,所以多个 yield 其实是个状态模式,第一个 yield 是状态 1,第二个 yield 是状态 2,每次访问时会基于状态知道当前应该执行哪一个 yield,取得哪一个值。
  • 从程序的角度讲,协程的核心就是迭代器。
  • 想要定义一个协程方法有两个因素,第一:方法的返回值为 IEnumerator 。第二,方法中有 yield关键字。
  • 当代码满足以上两个条件时,此方法的执行就具有了迭代器的特质,其核心就是 MoveNext方法。
  • 方法内的内容将会被分成两部分:yield 之前的代码和 yield 之后的代码。yield之前的代码会在第一次执行MoveNext时执行, yield之后的代码会在第二次执行MoveNext方法时执行。
  • 而在Unity中,MoveNext的执行时机是以帧为单位的,无论你是设置了延迟时间,还是通过按钮调用MoveNext,亦或是根本没有设置执行条件,Unity都会在每一帧的生命周期中判断当前帧是否满足当前协程所定义的条件,一旦满足,当前帧就会抽出CPU时间执行你所定义的协程迭代器的MoveNext。
  • 注意,只要方法中有yield语句,那么方法的返回值就必须是 IEnumerator ,不然无法通过编译。

5.线程和协程的区别

  • 协程:即协作式程序,其思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。协程实际上是在一个线程中,只不过每个协程对CPU进行分时,协程可以访问和使用unity的所有方法和component。同一时间只能执行某个协程。开辟多个协程开销不大。协程适合对某任务进行分时处理。
  • 线程:多线程是阻塞式的,每个IO都必须开启一个新的线程,但是对于多CPU的系统应该使用thread,尤其是有大量数据运算的时刻,但是IO密集型就不适合;而且thread中不能操作unity的很多方法和component。同一时间可以同时执行多个线程。开辟多条线程开销很大。线程适合多任务同时处理。

线程和协同程序的主要不同在于:在多处理器情况下,从概念上来讲多线程程序同时运行多个线程;而协同程序是通过协作来完成,在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只在必要时才会被挂起。
 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ranger UserSync

作用 同步User到RangerDb 架构 解析 启动一个while(True) 进程定时同步&#xff0c;程序入口 source sink 掉接口获取Ranger User 并且Cache 计算delta 同步

静态网页设计——清雅古筝网(HTML+CSS+JavaScript)

前言 声明&#xff1a;该文章只是做技术分享&#xff0c;若侵权请联系我删除。&#xff01;&#xff01; 感谢大佬的视频&#xff1a; https://www.bilibili.com/video/BV1T64y1K7Zn/?vd_source5f425e0074a7f92921f53ab87712357b 使用技术&#xff1a;HTMLCSSJS&#xff08;…

MybatisPlus—自定义SQL

目录 1. 自定义SQL介绍 2. 自定义SQL使用步骤 3. 自定义SQL实例 4.总结 1. 自定义SQL介绍 介绍&#xff1a;自定义SQL并不是由我们来编写全部SQL语句&#xff0c;而是通过利用MyBatisPlus的Wrapper来构建复杂的Where条件&#xff0c;然后自己定义SQL语句中剩下的部分。 使…

特种印制电路技术

1特种印制电路技术现状、分类及特点 2006年&#xff0c;信息产业部(现工信部)电子信息产品管理司将高档PCB产品类型概括为HDI板、多层FPC、刚挠结合板、IC载板、通信背板、特种板材印制板、印制板新品种等种类。但直至目前&#xff0c;在印制电路设计与制造领域还没有形成特种…

爆肝整理,接口测试+为什么要做接口测试总结,策底贯通...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、什么是接口测试…

面向对象进阶(static关键字,继承,方法重写,super,this)

文章目录 面向对象进阶部分学习方法&#xff1a;今日内容教学目标 第一章 复习回顾1.1 如何定义类1.2 如何通过类创建对象1.3 封装1.3.1 封装的步骤1.3.2 封装的步骤实现 1.4 构造方法1.4.1 构造方法的作用1.4.2 构造方法的格式1.4.3 构造方法的应用 1.5 this关键字的作用1.5.1…

KBDLT2.DLL文件丢失,软件或游戏无法启动,如何解决,试试这些方法,亲测有效

KBDLT2.DLL是Windows操作系统中的一个重要的动态链接库文件&#xff0c;它主要用于支持系统的键盘布局功能&#xff0c;尤其是与立陶宛语相关的键盘布局。 如果KBDLT2.DLL文件缺失或损坏&#xff0c;可能会导致一些问题。例如&#xff0c;当你试图使用立陶宛语键盘布局时&#…

springboot 2.7 oauth server配置源码走读一

springboot 2.7 oauth server配置源码走读 入口&#xff1a; 上述截图中的方法签名和OAuth2AuthorizationServerConfiguration类中的一个方法一样&#xff0c;只不过我们自己的配置类优先级比spring中的配置类低&#xff0c;算是配置覆盖&#xff0c;看下图所示&#xff1a; …

[蓝桥 2020]最长递增

最长递增 题目描述 在数列 a1​,a2​,⋯,an​ 中&#xff0c;如果 ai​<ai1​<ai2​<⋯<aj​&#xff0c;则称 ai​ 至 aj​ 为一段递增序列&#xff0c;长度为 j−i1。 定一个数列&#xff0c;请问数列中最长的递增序列有多长。 输入描述 输入的第一行包含一…

day10 用栈实现队列 用队列实现栈

题目1&#xff1a;232 用栈实现队列 题目链接&#xff1a;232 用栈实现队列 题意 用两个栈实现先入先出队列&#xff08;一个入栈&#xff0c;一个出栈&#xff09;&#xff0c;实现如下功能&#xff1a; 1&#xff09;push&#xff1a;将元素x推到队列末尾 2&#xff09;…

【Python学习】Python学习6-循环语句

目录 【Python学习】【Python学习】Python学习6-循环语句 前言for循环for循环示例通过序列索引迭代循环使用 else 语句 while循环while循环示例continue和 break无限循环循环使用 else 语句简单语句组 嵌套循环Python for 循环嵌套语法&#xff1a;Python while 循环嵌套语法&…

关键字:package关键字

在 Java 中&#xff0c;package关键字用于组织和管理类文件。它将类文件分组到不同的包中&#xff0c;以提供更好的代码组织和可读性。 以下是package关键字的用法&#xff1a; 1.package语句&#xff1a;在 Java 源代码的开头使用package关键字来声明当前类所属的包。例如&a…

【pytorch学习】 深度学习 教程 and 实战

pytorch编程实战博主&#xff1a;https://github.com/lucidrains https://github.com/lucidrains/vit-pytorch

有趣的前端知识(一)

推荐阅读 智能化校园&#xff1a;深入探讨云端管理系统设计与实现&#xff08;一&#xff09; 智能化校园&#xff1a;深入探讨云端管理系统设计与实现&#xff08;二&#xff09; 文章目录 推荐阅读HTML简介基础声明HTML标签标题段落注释水平线文本格式化标签超链接图像<i…

CSS 彩虹按钮效果

<template><view class"content"><button class"btn">彩虹按钮</button></view> </template><script></script><style>body{background-color: #000;}.content {margin-top: 300px;}.btn {width: 1…

python中的异常处理

目录 一&#xff1a;处理机制 二&#xff1a;实例 一&#xff1a;处理机制 Python中的异常处理主要通过try/except语句实现。try语句块包含可能会引发异常的代码&#xff0c;而except语句块包含在try块中发生异常时要执行的代码。此外&#xff0c;Python还提供了一个finally语…

强化学习1——多臂老虎机(上)

在强化学习中&#xff0c;关注智能体在与环境的交互中学习&#xff0c;成为试错型学习。多臂老虎机不存在状态信息&#xff0c;只有动作和奖励&#xff0c;是最简单的“和环境交互中学习“。 什么是多臂老虎机 老虎机_百度百科 (baidu.com) 多臂老虎机即有多个摇杆的老虎机&a…

原子性、CAS操作

Java中的原子性操作 所谓原子性操作&#xff0c;是指执行一系列操作时&#xff0c;这些操作要么全部执行&#xff0c;要么全部不执行&#xff0c;不存在只执行其中一部分的情况。 在设计计数器时一般都先读取当前值&#xff0c;然后1,再更新。 这个过程是读—改—写的过程&a…

Java学习苦旅(二十二)——MapSet

本篇博客将详细讲解Map和Set。 文章目录 搜索概念模型 MapMap.Entry<K, V>Map的常用方法说明TreeMap和HashMap的区别 Set常用方法说明TreeSet和HashSet的区别 结尾 搜索 概念 Map和set是一种专门用来进行搜索的容器或者数据结构&#xff0c;其搜索的效率与其具体的实例…

记一个React组件入参不当导致页面卡死的问题

一、问题描述 1.1 触发现象 点击按钮后页面卡死 1.2 最小 Demo CodeSandBox&#xff1a;https://codesandbox.io/p/sandbox/react-hook-component-stuck-755wcyinscode&#xff1a;https://inscode.csdn.net/ import ./App.css; import React, { useState, useEffect } f…