2023-08-04 Untiy进阶 C#知识补充4——C#5主要功能与语法

文章目录

        • 一、概述
        • 二、回顾——线程
        • 三、线程池
        • 四、Task 任务类
        • 五、同步和异步

​ 注意:在此仅提及 Unity 开发中会用到的一些功能和特性,对于不适合在 Unity 中使用的内容会忽略。

一、概述

  • C# 5
    • 调用方信息特性(C# 进阶内容)
    • 异步方法 async 和 await

二、回顾——线程

  1. Unity 支持多线程
  2. Unity 中开启的多线程不能使用主线程中的对象
  3. Unity 中开启多线程后一定记住关闭
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;

public class Lesson4 : MonoBehaviour
{
    Thread t;
    
    // Start is called before the first frame update
    void Start()
    {
        t = new Thread(()=> {
            while (true) {
                print("123");
                Thread.Sleep(1000);
            }
        });
        t.Start(); // 开启线程
        print("主线程执行");
    }

    private void OnDestroy()
    {
        t.Abort(); // 关闭线程
    }
}

三、线程池

​ 命名空间:System.Threading

​ 类名:ThreadPool

​ 在多线程的应用程序开发中,频繁地创建删除线程会带来性能消耗,产生内存垃圾。为了避免这种开销,C# 推出了线程池 ThreadPool 静态类。

​ ThreadPool 中有若干数量的线程,如果有任务需要处理时,会从线程池中获取一个空闲的线程来执行任务。任务执行完毕后线程不会销毁,而是被线程池回收以供后续任务使用。

​ 当线程池中所有的线程都在忙碌时,又有新任务要处理时,线程池才会新建一个线程来处理该任务;如果线程数量达到设置的最大值,任务会排队,等待其他任务释放线程后再执行。

​ 线程池能减少线程的创建,节省开销,可以减少 GC 垃圾回收的触发。

​ 线程池相当于就是一个专门装线程的缓存池(Unity小框架套课中有对缓存池的详细讲解)

  • 优点:节省开销,减少线程的创建,进而有效减少 GC 触发
  • 缺点:不能控制线程池中线程的执行顺序,也不能获取线程池内线程取消 / 异常 / 完成的通知
  1. 获取可用的工作线程数和 I/O 线程数

    int num1, num2;
    ThreadPool.GetAvailableThreads(out num1, out num2);
    
    print(num1); // 2000
    print(num2); // 200
    
  2. 获取线程池中工作线程的最大数目和 I/O 线程的最大数目

    ThreadPool.GetMaxThreads(out num1, out num2);
    
    print(num1); // 2000
    print(num2); // 200
    
  3. 设置线程池中可以同时处于活动状态的工作线程的最大数目和 I/O 线程的最大数目

    // 大于次数的请求将保持排队状态,直到线程池线程变为可用
    // 更改成功返回true,失败返回false
    if(ThreadPool.SetMaxThreads(20, 20)) {
        print("更改成功");
    }
    
    ThreadPool.GetMaxThreads(out num1, out num2);
    
    print(num1); // 20
    print(num2); // 20
    
  4. 获取线程池中工作线程的最小数目和 I/O 线程的最小数目

    ThreadPool.GetMinThreads(out num1, out num2);
    
    print(num1); // 16
    print(num2); // 16
    
  5. 设置工作线程的最小数目和 I/O 线程的最小数目

    if(ThreadPool.SetMinThreads(5, 5)) {
        print("设置成功");
    }
    
    ThreadPool.GetMinThreads(out num1, out num2);
    
    print(num1); // 5
    print(num2); // 5
    
  6. 将方法排入队列以便执行,当线程池中线程变得可用时执行

    • public static bool QueueUserWorkItem(WaitCallback callBack)
    • public static bool QueueUserWorkItem(WaitCallback callBack, object state)

    其中,state 为 callBack 的参数,不传则默认为 null。

    for (int i = 0; i < 10; i++) {
        ThreadPool.QueueUserWorkItem((obj) => {
            print("第" + obj + "个任务");
        }, i);
    }
    
    print("主线程执行");
    

    从运行结果可看出,控制线程池中线程的执行顺序不确定:

    image-20230619145519494
图1 线程池中线程的执行顺序

四、Task 任务类

​ 命名空间:System.Threading.Tasks

​ 类名:Task

​ Task 是在线程池基础上进行的改进,它拥有线程池的优点,同时解决了使用线程池不易控制的弊端。

​ 它是基于线程池的优点对线程的封装,可以让我们更方便高效的进行多线程开发,一个 Task 对象就是一个线程。

(一)创建无返回值的 Task

​ 本质上是从线程池中取出一个线程进行执行。

  1. 使用 new 传入委托函数

    Task t1 = new Task(() => {
        print("方式一创建");
    });
    
    t1.Start(); // 手动开启
    
  2. 使用 Task 中的 Run 静态方法传入委托函数

    Task t2 = Task.Run(() => { // 直接开启
        print("方式二创建");
    });
    
  3. 使用 Task.Factory 中的 StartNew 静态方法传入委托函数

    Task t3 = Task.Factory.StartNew(() => {
        print("方式三创建");
    });
    

(二)创建有返回值的 Task

​ 在上述基础上添加返回类型的泛型即可。

  1. 使用 new 传入委托函数

    Task t1 = new Task<int>(() => {
        print("方式一创建");
        return 1;
    });
    
    t1.Start(); // 手动开启
    
  2. 使用 Task 中的 Run 静态方法传入委托函数

    Task t2 = Task.Run<string>(() => { // 直接开启
        print("方式二创建");
        return "2";
    });
    
  3. 使用 Task.Factory 中的 StartNew 静态方法传入委托函数

    Task t3 = Task.Factory.StartNew<float>(() => {
        print("方式三创建");
        return 3.0f;
    });
    
  • 获取返回值

    print(t1.Result); // 1
    print(t2.Result); // 2
    print(t3.Result); // 3
    

    注意:

    ​ Result 获取结果时会阻塞线程,如果 task 没有执行完成,会等待 task 执行完成获取到 Result 然后再执行后边的代码。

(三)同步执行 Task

​ 使用上述三种 Start、Run 和 StartNew 方法会在创建时异步启动 Task。

​ 如果需要同步执行,则只能使用 new Task 的方式并使用 RunSynchronously 方法。

  1. 异步执行

    Task t = new Task(()=> {
        Thread.Sleep(1000);
        print("哈哈哈");
    });
    t.Start();
    
    print("主线程执行");
    
    image-20230619151822601
    图2 异步执行结果
  2. 同步执行

    Task t = new Task(()=> {
        Thread.Sleep(1000);
        print("哈哈哈");
    });
    t.RunSynchronously();
    
    print("主线程执行");
    
    image-20230619151946027
    图3 同步执行结果

(四)阻塞 Task

  1. Wait:等待任务执行完毕,再执行后面的内容。

    Task t1 = Task.Run(() => {
        for (int i = 0; i < 5; i++) {
            print("t1:" + i);
        }
    });
    t1.Wait();
    
    print("主线程执行");
    

    当 t1 线程执行完毕后,才会执行主线程中的打印内容。

    image-20230619152500934
    图4 Wait执行结果
  2. WaitAny:传入任务中任意一个任务结束就继续执行。

    Task t1 = Task.Run(() => {
        for (int i = 0; i < 5; i++) {
            print("t1:" + i);
        }
    });
    Task t2 = Task.Run(() => {
        for (int i = 0; i < 50; i++) {
            print("t2:" + i);
        }
    });
    Task.WaitAny(t1, t2);
    
    print("主线程执行");
    

    在这里,t1 执行完成后将会执行主线程的打印,但是 t2 线程仍继续执行。因为主线程与 t2 线程先后顺序无法控制,因此 t1 线程执行完成后没有立即打印主线程的内容。

    image-20230619153014365
    图5 WaitAny执行结果
  3. WaitAll:任务列表中所有任务执行结束就继续执行。

    t1、t2 线程都执行完成后,主线程才打印内容。

    image-20230619153154656
    图6 WaitAll执行结果

(五)延续 Task

  1. 传入任务完毕后再执行某任务

    • WhenAll + ContinueWith

      • WhenAll:创建一个任务,该任务将在所有提供的任务完成后完成。

        public static Task WhenAll(params Task[] tasks)

      • ContinueWith:创建在目标任务完成时异步执行的延续。

        public Task ContinueWith(Action<Task> continuationAction)

    Task.WhenAll(t1, t2).ContinueWith((t) => {
        print("一个新的任务开始了");
    });
    
    • ContinueWhenAll:创建在一组指定任务完成时启动的延续任务。

      public Task ContinueWhenAll(Task[] tasks, Action<Task[]> continuationAction)

    Task.Factory.ContinueWhenAll(new Task[] { t1, t2 }, (t) => {
        print("一个新的任务开始了");
    });
    
  2. 传入任务只要有一个执行完毕后再执行某任务

    • WhenAny + ContinueWith

      • WhenAny:创建一个任务,该任务将在提供的任何任务完成时完成。

        public static Task WhenAny(params Task[] tasks)

      • ContinueWith:创建在目标任务完成时异步执行的延续。

        public Task ContinueWith(Action<Task> continuationAction)

    Task.WhenAny(t1, t2).ContinueWith((t) => {
        print("一个新的任务开始了");
    });
    
    • ContinueWhenAny:创建一个延续任务,该任务将在提供集中的任何任务完成后启动。

      public Task ContinueWhenAny(Task[] tasks, Action<Task[]> continuationAction)

    Task.Factory.ContinueWhenAny(new Task[] { t1, t2 }, (t) => {
        print("一个新的任务开始了");
    });
    

(六)取消 Task

  1. 加入 bool 标识,控制线程内死循环的结束

    public class Lesson5 : MonoBehaviour
    {
        private bool isRuning = true; // 循环标识
    
        public Task t;
        
        // Start is called before the first frame update
        private void Start() 
        {
            t = Task.Run(() => {
                print("一个新的任务开始了");
                int i = 0;
                while (isRuning) { // 通过 isRuning 标识控制循环
                    print(i++);
                    Thread.Sleep(1000);
                }
            });
        }
        
        // Update is called once per frame
        void Update() 
        {
            if (Input.GetKeyDown(KeyCode.Space)) { // 按下空格停止线程
                isRuning = false;
            }
        }
    }
    
  2. CancellationTokenSource:取消令牌(标识)源类

    • 控制循环取消

      IsCancellationRequested:获取是否已请求取消此取消令牌源。

    public class Lesson5 : MonoBehaviour
    {
        public CancellationTokenSource c;
    
        public Task t;
        
        // Start is called before the first frame update
        private void Start() 
        {
            t = Task.Run(() => {
                print("一个新的任务开始了");
                int i = 0;
                while (!c.IsCancellationRequested) { // IsCancellationRequested 默认为 false
                    print(i++);
                    Thread.Sleep(1000);
                }
            });
        }
        
        // Update is called once per frame
        void Update() 
        {
            if (Input.GetKeyDown(KeyCode.Space)) { // 按下空格停止线程
                c.Cancel();                        // 使用 Cancel 方法停止
            }
        }
    }
    
    • 延迟取消

      CancelAfter:在指定的毫秒数后计划对此取消令牌源执行取消操作。

    public class Lesson5 : MonoBehaviour
    {
        public CancellationTokenSource c;
    
        public Task t;
        
        // Start is called before the first frame update
        private void Start() 
        {
            t = Task.Run(() => {
                print("一个新的任务开始了");
                int i = 0;
                while (!c.IsCancellationRequested) {
                    print(i++);
                    Thread.Sleep(1000);
                }
            });
        }
        
        // Update is called once per frame
        void Update() 
        {
            if (Input.GetKeyDown(KeyCode.Space)) {
                c.CancelAfter(5000); // 延迟 5s 取消
            }
        }
    }
    
    • 取消后执行逻辑

      Token.Register:注册取消此取消令牌时将调用的委托。

    public class Lesson5 : MonoBehaviour
    {
        public CancellationTokenSource c;
    
        public Task t;
        
        // Start is called before the first frame update
        private void Start() 
        {
            t = Task.Run(() => {
                print("一个新的任务开始了");
                int i = 0;
                while (!c.IsCancellationRequested) {
                    print(i++);
                    Thread.Sleep(1000);
                }
            });
            
            c.Token.Register(() => { print("任务取消了"); }); // 取消回调,线程被取消后将执行打印
        }
        
        // Update is called once per frame
        void Update() 
        {
            if (Input.GetKeyDown(KeyCode.Space)) {
                c.CancelAfter(5000); // 延迟 5s 取消
            }
        }
    }
    

(七)小结

  1. Task 类是基于 Thread 的封装
  2. Task 类可以有返回值,Thread 没有返回值
  3. Task 类可以执行后续操作,Thread 没有这个功能
  4. Task 可以更加方便的取消任务,Thread 相对更加单一
  5. Task 具备 ThreadPool 线程池的优点,更节约性能

五、同步和异步

​ 同步和异步主要用于修饰方法

  • 同步方法:
    当一个方法被调用时,调用者需要等待该方法执行完毕后返回才能继续执行。
  • 异步方法:
    当一个方法被调用时立即返回,并获取一个线程执行该方法内部的逻辑,调用者不用等待该方法执行完毕。

​ 简单理解:把一些不需要立即得到结果且耗时的逻辑设置为异步执行,可以提高程序的运行效率,避免由于复杂逻辑带来的的线程阻塞。

​ 需要处理的逻辑会严重影响主线程执行的流畅性时,需要使用异步编程,比如:

  1. 复杂逻辑计算时
  2. 网络下载、网络通讯
  3. 资源加载时
  4. 等等

(一)async 关键字

​ async 和 await 一般需要配合 Task 进行使用。

​ async 用于修饰函数、lambda 表达式、匿名函数,表示该方法是一个异步方法。

public class Lesson6 : MonoBehaviour 
{
    void start() {
        Test(); // 打印 "123"
    }
    
    public async void Test() { // 方法中没有 await 关键字,则视为同步方法
        print("123");
    }
}

​ 上述代码声明了一个方法 Test(),在 void 前面添加关键字 async,表示该方法是异步的。在该方法内没有 await 关键字,因此编译器会发出警告,并将该方法默认视为同步方法。

​ 声明异步方法时,最好在函数名称后加上 Aysnc,以表示该方法为异步方法。

​ 下面总结了几点说明:

  1. 在异步方法中使用 await 关键字(不使用编译器会给出警告但不报错),否则异步方法会以同步方式执行;
  2. 异步方法名称建议以 Async 结尾;
  3. 异步方法的返回值只能是 void、Task、Task<>;
  4. 异步方法中不能声明使用 ref 或 out 关键字修饰的变量。

(二)await 关键字

​ await 用于在函数中和 async 配对使用,主要作用是等待某个逻辑结束。
​ 此时逻辑会返回函数外部继续执行,直到等待的内容执行结束后,再继续执行异步函数内部逻辑。

​ 在一个 async 异步函数中可以有多个 await 等待关键字。

​ 使用 await 等待异步内容执行完毕(一般和 Task 配合使用)遇到 await 关键字时:

  1. 异步方法将被挂起;
  2. 将控制权返回给调用者;
  3. 当 await 修饰内容异步执行结束后,继续通过调用者线程执行后面内容。
public class Lesson6 : MonoBehaviour 
{
    void start() {
        print("1");
        
        TestAsync();
        
        print("2");
    }
    
    public async void TestAsync()
    {
        print("3");      // 1
        
        await Task.Run(() => {    // 2
            Thread.Sleep(5000);
        });
        
        print("4"); // 3
    }
}

​ 上述代码执行时,先打印主函数中的 “1”,然后进入 TestAsync() 异步函数,打印 “3”,遇到 await 关键字后,开启新的进程执行进程代码,TestAsync() 方法被挂起,执行主函数后面的代码,即接着打印 “2”。直到 Task 任务完成后才继续 TestAsync() 方法后面的代码,因此最后打印 “4”。

(三)举例

  1. 使用异步方法模拟复杂寻路计算:
public class Lesson6 : MonoBehaviour 
{
    void start() {
        // 利用Task新开线程进行计算 计算完毕后再使用 比如复杂的寻路算法
        CalcPathAsync(this.gameObject, Vector3.zero);
    }
    
    public async void CalcPathAsync(GameObject obj, Vector3 endPos) {
        print("开始处理寻路逻辑");
        
        int value = 10;
        await Task.Run(() => {
            Thread.Sleep(1000); // 处理复杂逻辑计算,通过休眠模拟计算的复杂性
            value = 50;
            // 不能在多线程里访问Unity主线程场景中的对象,这样写会报错
            // print(obj.transform.position); xxx
        });

        print("寻路计算完毕 处理逻辑" + value);
        obj.transform.position = Vector3.zero;
    }
}
  1. 使用异步方法实现简单计时器:
public class Lesson6 : MonoBehaviour 
{
    CancellationTokenSource source;
    
    void start() {
        TimerAsync();
    }
    
    public async void TimerAsync()
    {
        source = new CancellationTokenSource();
        int i = 0;
        while (!source.IsCancellationRequested) {
            print(i);
            await Task.Delay(1000); // 延时 1000 ms
            ++i;
        }
    }
}

(四)资源加载

​ (Addressables 的资源异步加载是可以使用 async 和 await 的)

​ Unity 中大部分异步方法是不支持异步关键字 async 和 await 的,我们只有使用协同程序进行使用。
​ 虽然官方不支持,但是存在第三方的工具(插件)可以让 Unity 内部的一些异步加载的方法支持 异步关键字:https://github.com/svermeulen/Unity3dAsyncAwaitUtil。

​ 虽然 Unity 中的各种异步加载对异步方法支持不太好,但是当我们用到 .Net 库中提供的一些 API 时,可以考虑使用异步方法

  1. Web 访问:HttpClient;
  2. 文件使用:StreamReader、StreamWriter、JsonSerializer、XmlReader、XmlWriter 等等;
  3. 图像处理:BitmapEncoder、BitmapDecoder。

​ 一般 .Net 提供的 API 中,方法名后面带有 Async 的方法都支持异步方法。

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

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

相关文章

【大数据】Flink 详解(二):核心篇 Ⅰ

Flink 详解&#xff08;二&#xff09;&#xff1a;核心篇 Ⅰ 14、Flink 的四大基石是什么&#xff1f; ​ Flink 的四大基石分别是&#xff1a; Checkpoint&#xff08;检查点&#xff09;State&#xff08;状态&#xff09;Time&#xff08;时间&#xff09;Window&#xff…

54款宝藏级AIGC工具分享(claude,Midjourney,Stable Diffusion等)

随着ChatGPT的一波又一波高潮&#xff0c;生成式AI逐渐进入人们视野&#xff0c;并开始大行其道&#xff0c;正如人们所说&#xff1a;AI用的好&#xff0c;天天下班早&#xff01; 当然&#xff0c;有效的利用AI不但能下班早&#xff0c;还能在上班时间摸鱼&#xff0c;就如潘…

【Docker】数据库动态授权组件在Kubernetes集群下的测试过程记录

目录 背景 组件原理 测试设计 环境 测试脚本 脚本build为linux可执行文件 镜像构建 Dockerfile Docker build 镜像有效性验证 总结 资料获取方法 背景 我们都知道出于安全性考虑&#xff0c;生产环境的权限一般都是要做最小化控制&#xff0c;尤其是数据库的操作授…

2023年华数杯数学建模B题思路代码分析 - 不透明制品最优配色方案设计

# 1 赛题 B 题 不透明制品最优配色方案设计 日常生活中五彩缤纷的不透明有色制品是由着色剂染色而成。因此&#xff0c;不透明 制品的配色对其外观美观度和市场竞争力起着重要作用。然而&#xff0c;传统的人工配色 存在一定的局限性&#xff0c;如主观性强、效率低下等。因此…

华三H3C S5120V3交换机的配置之组建IRF

IRF&#xff08;Intelligent Resilient Framework&#xff0c;智能弹性架构&#xff09;&#xff0c;是华三交换机实现虚拟堆叠的一种技术&#xff0c;其核心思想是将多台交换机连接在一起&#xff0c;虚拟成一台交换机&#xff0c;进而实现统一管理。和传统的堆叠概念不同&…

Python如何解决Amazon亚马逊“图文验证码”识别(6)

前言 本文是该专栏的第55篇,后面会持续分享python爬虫干货知识,记得关注。 在本专栏前面,笔者有详细介绍多种登录验证码识别方法,感兴趣的同学可往前翻阅。而本文,笔者将单独详细介绍亚马逊Amazon的图文识别验证码的解决方法。 如上图所示,访问或请求频次达到一定程度之…

笔记本WIFI连接无网络【实测有效,不用重启电脑】

笔记本Wifi连接无网络实测有效解决方案 问题描述&#xff1a; 笔记本买来一段时间后&#xff0c;WIFI网络连接开机一段时间还正常连接&#xff0c;但是过一段时间显示网络连接不上&#xff0c;重启电脑太麻烦&#xff0c;选择编写重启网络脚本解决。三步解决问题。 解决方案&a…

FastAPI(七)应用配置

目录 一、在apps下新建文件夹config 二、新建配置文件app_conf.py 一、在apps下新建文件夹config 二、新建配置文件app_conf.py from functools import lru_cachefrom pydantic.v1 import BaseSettingsclass AppConfig(BaseSettings):app_name: str "Windows10 插件&qu…

JMeter启动时常见的错误

很多小伙伴在学工具这一块时&#xff0c;安装也是很吃力的一个问题&#xff0c;之前记得有说过怎么安装jmeter这个工具。那么你要启动jmeter的时候&#xff0c;一些粉丝就会碰到如下几个问题。 1.解压下载好的jmeter安装&#xff0c;Windows 平台&#xff0c;双击 jmeter/bin …

谷粒商城第十天-分组新增级联显示商品分类分组修改级联回显商品分类

目录 一、总述 二、前端实现 三、后端实现 四、总结 一、总述 本次就是一个小的优化。 就是分组新增或者是修改的时候&#xff0c;直接显示商品分类的id可读性不高&#xff0c;新增的时候需要填写对商品分类的id&#xff0c;修改的时候&#xff0c;就只是给你一个商品分类…

HarmonyOS/OpenHarmony-ArkTS基于API9元服务开发快速入门

一、创建项目 二、创建卡片 三、添加资源 四、具体代码 Entry Component struct WidgetNewCard {/** The title.*/readonly TITLE: string harmonyOs;readonly CONTEXT: string 技术构建万物智联;/** The action type.*/readonly ACTION_TYPE: string router;/** The…

【快应用】list组件如何区分滑动的方向?

【关键词】 list组件、滑动方向、scroll 【问题背景】 有cp反馈list这个组件在使用的时候&#xff0c;不知道如何区分它是上滑还是下滑。 【问题分析】 list组件除了通用事件之外&#xff0c;还提供了scroll、scrollbottom、scrolltop、scrollend、scrolltouchup事件&#x…

ArduPilot开源飞控之MAVProxy简介

ArduPilot开源飞控之MAVProxy简介 1. 源由2. 特点3. 安装 & 更新3.1 安装Step 1: 烧录raspberryPi镜像Step 2&#xff1a;apt软件包更新Step 3&#xff1a;Raspian系统更新Step 4&#xff1a;安装依赖环境Step 5&#xff1a;安装mavproxyStep 6&#xff1a;配置bash环境 3.…

后台管理系统

1.1 项目概述 简易后台管理系统是一个基于Vue3ElemrntPlus的后台管理系统&#xff0c;提供了用户登录、记住密码、数据的增删改查、分页、错误信息提示等功能&#xff0c;旨在协助管理员对特定数据进行管理和操作。 没有后台对接&#xff0c;数据源为假数据。 全部代码已上传G…

关于HIVE的分区与分桶

1.分区 1.概念 Hive中的分区就是把一张大表的数据按照业务需要分散的存储到多个目录&#xff0c;每个目录就称为该表的一个分区。在查询时通过where子句中的表达式选择查询所需要的分区&#xff0c;这样的查询效率会提高很多 个人理解白话:按表中或者自定义的一个列,对数据进…

整理mongodb文档:集合名字有类似-等特殊字符串如何处理?

个人博客 整理mongodb文档:集合名字有类似-等特殊字符串如何处理&#xff1f; 首先&#xff0c;先创建一个collection的名字为’collection-test’&#xff0c;这个表名中&#xff0c;colletion的名字有一个特殊字符串“-”&#xff0c;但是后面插入数据的时候会有这么一个错…

rk3399移植linux kernel

rk3399移植linux kernel 0.前言一、移植ubuntu根文件系统二、移植linux1.支持NFS(可选)2.配置uevent helper3.支持etx4文件系统(默认已支持)4.配置DRM驱动5.有线网卡驱动6.无线网卡驱动 三、设备树四、内核镜像文件制作五、烧录六、总结 参考文章&#xff1a; 1.RK3399移植u-bo…

无涯教程-Perl - defined函数

描述 如果 EXPR 的值不是undef值,则此函数返回true&#xff1b;如果未指定 EXPR ,则检查$_的值。它可以与许多功能一起使用以检测操作失败,因为如果出现问题,它们将返回undef。简单的布尔测试不会区分false,零,空字符串或字符串.0。 如果 EXPR 是函数或函数引用,则在定义函数…

第八篇: K8S Prometheus Operator实现Ceph集群企业微信机器人告警

Prometheus Operator实现Ceph集群企业微信告警 实现方案 我们的k8s集群与ceph集群是部署在不同的服务器上&#xff0c;因此实现方案如下&#xff1a; (1) ceph集群开启mgr内置的exporter服务&#xff0c;用于获取ceph集群的metrics (2) k8s集群通过 Service Endponit Ser…

推荐5本软件测试人员必读经典书籍

学会选择对的学习方法 俗话说&#xff1a;“选择大于努力”。 初学软件测试也如此。很多刚入行测试的同学最容易陷入一个误区&#xff0c;那就是优先买一堆视频来学习。结果时间过去了&#xff0c;视频仅以形式主义存在电脑的硬盘里&#xff0c;从此走上了入门到放弃之路。 …