在Hierarchy层级面板上组织业务脚本流程——使用async和await

news2024/11/18 6:17:33

〇、把多个不同的脚本串联在一起顺序阻塞执行

把很多个脚本串联在一起,让他们按照先后顺序执行,等着前面的执行完毕,在执行后面的,这就是用【异步方法】实现的,能够顺序执行的流程。
如下图所示,流程脚本都绑定在空物体上,然后绑了脚本的所有空物体都是脚本物体,把他们集中统一管理。
一共有14个脚本,他们会按照先后顺序有条不紊的排队执行。
在这里插入图片描述

一、多个脚本按照先后顺序排队执行是如何实现的——原理

(1)流程按照顺序先后执行

既然要排队执行,那必定是后面的等着前面的执行完毕之后自己才执行,这就必须用到【等待await】功能,本文用[异步方法]而不是直接用[协程]。

(2)不同的脚本(Class)如何给他们制定一个统一的入口方法

  • 给每个脚本添加一个【名字和签名】都一样的方法,调用的时候,直接Call这个方法,因为Class的类型不一样,所以在调用的时候,可能需要用到【反射】。能不能用一种偷懒的方法,直接调用FlowAsync( )呢?那就是下面介绍的方法:实现一个统一的接口

给所有的脚本都定义一个签名完全一致的普通方法作为调用的接口:

public class MyScript: MonoBehaviour
{
	public async UniTask FlowAsync(CancellationToken ctk){}
}

(3)不同的脚本(Class)如何给他们强制实现一个入口方法——接口

注意下面的脚本,除了继承祖传的MonoBehaviour,它还继承了IFlowAsync,IFlowAsync是什么鬼东西呢?看下文分解!

所有的脚本都实现了一个统一的接口IFlowAsync.FlowAsync

public class MyScript: MonoBehaviour,IFlowAsync
{
	public async UniTask FlowAsync(CancellationToken ctk){}
}

顾名思义,IFlowAsync就是一个接口啊,且看它的代码:

定义一个接口,包含一个叫FlowAsync的异步方法,所以继承该接口的脚本,都必须定义一个同名的方法,也就是实现接口。如果我只继承接口而不实现接口会怎么样,当然是报错。

using System.Threading;
using Cysharp.Threading.Tasks;

/// <summary>
/// 接口:定义一个叫FlowAsync的异步方法
/// </summary>
public interface IFlowAsync
{
    public UniTask FlowAsync(CancellationToken ctk);
}

只继承接口,而不实现接口的后果:
在这里插入图片描述

二、多个脚本按照先后顺序排队执行是如何实现的——代码

需求:如下图所示,我有14条流程,它们都挂在14个空物体上,这些空物体都挂在另一个叫【流程内容】的空物体下。当我启动【流程内容】节点上的脚本时,该脚本自动加载下面的子物体,然后按照先后顺序执行子物体上的脚本。
在这里插入图片描述

(1)流程脚本的一个父节点如何实现?

  • 父脚本定义一个【脚本列表】,用来装子物体的脚本
    /// <summary>
    /// 流程脚本列表
    /// </summary>
    public List<MonoBehaviour> scripts = new List<MonoBehaviour>();
  • 流程启动时,顺序执行【脚本列表】中的所有脚本,等待一个脚本执行完毕,再执行下一个脚本
foreach (var script in scripts)
{
    if (script == null) continue;
    var scriptName = script.name;
    Debug.Log($"**********************开始步骤:{scriptName}");

    await (script as IFlowAsync).FlowAsync(ctk);
    Debug.Log($"**********************完成步骤:{scriptName}");
}

注意其中的一行代码

await (script as IFlowAsync).FlowAsync(ctk);

代码解释:这一句调用了各个脚本的接口方法——FlowAsync(),古人说【人上一百,形形色色】,脚本上一百也不例外。用了接口的好处就是用【as】操作符,把脚本转成接口类型,然后直接call接口的方法,这样就省去反射捕捉具体的Class类型的操作了。

阻塞执行的诀窍:用【await】关键字来等待一个【async】方法执行

  • 其它功能:编辑器状态自动添加子物体,捕捉他们的脚本。子流程进行编号。子流程可以单步测试。

加载子物体上的步骤脚本

/// <summary>
/// 加载子物体上的步骤脚本
/// </summary>
#if UNITY_EDITOR
[ContextMenu("加载步骤")]
#endif
void LoadSteps()
{
    scripts.Clear();
    var root = this.gameObject.transform;
    int childCount = root.childCount;
    for (int i = 0; i < childCount; i++)
    {
        Transform childTransform = root.GetChild(i);

        //处理子物体
        Debug.Log(childTransform.name);

        //获取脚本,隐藏的不获取
        if (childTransform.gameObject.activeSelf)
        {
            var script = childTransform.GetComponent<MonoBehaviour>();
            scripts.Add(script);
        }
    }
}

步骤编号:加载进来的子物体,给他们编个序号,从1到N

/// <summary>
/// 步骤编号:加载进来的子物体,给他们编个序号,从1到N
/// </summary>
/// <returns></returns>
#if UNITY_EDITOR
[ContextMenu("步骤编号")]
#endif
async UniTask Test2()
{
    int i = 0;
    foreach (var script in scripts)
    {
        var name = script.name;
        string newName = "";
        if (script.name.Contains("】"))
        {
            var nameRight = name.Split('】')[1];
            newName = $"【{i}{nameRight}";
        }
        else
        {
            newName = $"【{i}{name}";
        }
        script.name = newName;
        i++;
    }
}

测试步骤:单步测试该步骤,注意编辑器模式下的修改很难撤销,所以在Running模式下随便测

/// <summary>
/// 测试步骤:单步测试该步骤,注意编辑器模式下的修改很难撤销,所以在Running模式下随便测
/// </summary>
#if UNITY_EDITOR
[ContextMenu("测试步骤")]
#endif
void testAsync()
{
    var ctsInfo = TaskSingol.CreatCts();
    FlowAsync(ctsInfo.cts.Token);
}

public async UniTask FlowAsync(CancellationToken ctk)
{

    try
    {
        foreach (var script in scripts)
        {
            if (script == null) continue;
            var scriptName = script.name;
            Debug.Log($"**********************开始步骤:{scriptName}");

            await (script as IFlowAsync).FlowAsync(ctk);
            Debug.Log($"**********************完成步骤:{scriptName}");
        }
    }
    catch (Exception e)
    {
        Debug.Log($"{this_name}报错:{e.Message}");
        Debug.Log($"\n 抛出一个OperationCanceledException");
        throw new OperationCanceledException();
    }        
}

(2)流程脚本的一个子节点如何实现?

作为一个子节点,满足一个条件即可——继承接口IFlowAsync并实现方法FlowAsync,下面是一个简单的例子:

using System.Threading;
using UnityEngine;
using Cysharp.Threading.Tasks;

public class FlowA : MonoBehaviour, IFlowAsync
{
    public async UniTask FlowAsync(CancellationToken ctk)
    {
        Debug.Log($"我是monobehaviourA {Time.realtimeSinceStartup}");
        await UniTask.Delay(2000,cancellationToken:ctk);
        Debug.Log($"UniTask.Delay(2000) {Time.realtimeSinceStartup}");
    }
}

三、流程的组织举例

主流程
在这里插入图片描述
其中第二个脚本节点包含子流程
在这里插入图片描述

四、部分代码清单

(1)流程父节点Step清单

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Cysharp.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Threading;

/// <summary>
/// 流程的节点,该节点下面挂的直接子物体【脚本】都属于该节点的子节点
/// </summary>
public class Step : MonoBehaviour,IFlowAsync
{
    /// <summary>
    /// 流程脚本列表
    /// </summary>
    public List<MonoBehaviour> scripts = new List<MonoBehaviour>();

    /// <summary>
    /// 流程的名字
    /// </summary>
    public string this_name;

    // Start is called before the first frame update
    void Start()
    {
        this_name = this.name;
        LoadSteps();
    }

    /// <summary>
    /// 加载步骤子物体上的步骤
    /// </summary>
#if UNITY_EDITOR
    [ContextMenu("加载步骤")]
#endif
    void LoadSteps()
    {
        scripts.Clear();
        var root = this.gameObject.transform;
        int childCount = root.childCount;
        for (int i = 0; i < childCount; i++)
        {
            Transform childTransform = root.GetChild(i);

            //处理子物体
            Debug.Log(childTransform.name);

            //获取脚本,隐藏的不获取
            if (childTransform.gameObject.activeSelf)
            {
                var script = childTransform.GetComponent<MonoBehaviour>();
                scripts.Add(script);
            }
        }
    }

    /// <summary>
    /// 步骤编号:加载进来的子物体,给他们编个序号,从1到N
    /// </summary>
    /// <returns></returns>
#if UNITY_EDITOR
    [ContextMenu("步骤编号")]
#endif
    async UniTask Test2()
    {
        int i = 0;
        foreach (var script in scripts)
        {
            var name = script.name;
            string newName = "";
            if (script.name.Contains("】"))
            {
                var nameRight = name.Split('】')[1];
                newName = $"【{i}{nameRight}";
            }
            else
            {
                newName = $"【{i}{name}";
            }
            script.name = newName;
            i++;
        }
    }

    /// <summary>
    /// 测试步骤:单步测试该步骤,注意编辑器模式下的修改很难撤销,所以在Running模式下随便测
    /// </summary>
#if UNITY_EDITOR
    [ContextMenu("测试步骤")]
#endif
    void testAsync()
    {
        var ctsInfo = TaskSingol.CreatCts();
        FlowAsync(ctsInfo.cts.Token);
    }

    public async UniTask FlowAsync(CancellationToken ctk)
    {

        try
        {
            foreach (var script in scripts)
            {
                if (script == null) continue;
                var scriptName = script.name;
                Debug.Log($"**********************开始步骤:{scriptName}");

                await (script as IFlowAsync).FlowAsync(ctk);
                Debug.Log($"**********************完成步骤:{scriptName}");
            }
        }
        catch (Exception e)
        {
            Debug.Log($"{this_name}报错:{e.Message}");
            Debug.Log($"\n 抛出一个OperationCanceledException");
            throw new OperationCanceledException();
        }        
    }
}

(2)物体绕自身某个轴的旋转

using Cysharp.Threading.Tasks;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;

/// <summary>
/// 异步方法:物体绕自身的某个轴旋转
/// </summary>
public class Rotate : MonoBehaviour,IFlowAsync
{
    /// <summary>
    /// 旋转的物体
    /// </summary>
    [SerializeField][Header("旋转的物体")]
    public GameObject target;

    /// <summary>
    /// 旋转轴
    /// </summary>
    [SerializeField]
    [Header("旋转轴")]
    public Vector3 axis;

    /// <summary>
    /// 速度
    /// </summary>
    [SerializeField]
    [Header("速度")]
    public float speed;

    /// <summary>
    /// 耗时
    /// </summary>
    [SerializeField]
    [Header("耗时")]
    public float duration;

    /// <summary>
    /// 旋转完毕恢复初始方位
    /// </summary>
    [SerializeField]
    [Header("旋转完毕恢复初始方位")]
    public bool restored = true;

#if UNITY_EDITOR
    [ContextMenu("测试")]
#endif
    void Test()
    {
        var ctsInfo = TaskSingol.CreatCts();
        FlowAsync(ctsInfo.cts.Token);
    }

    /// <summary>
    /// 自身旋转
    /// </summary>
    /// <param name="ctk"></param>
    /// <returns></returns>
    public async UniTask FlowAsync(CancellationToken ctk)
    {
        Debug.Log($"~~启动Rotate() {Time.realtimeSinceStartup}");
        await target.DoRotate(axis, speed, duration,ctk,restored);
        Debug.Log($"~~结束Rotate() {Time.realtimeSinceStartup}");
    }

    void OnDestroy()
    {
        TaskSingol.CancelAllTask();
    }
}

  • 上面代码中用到的DoRotate方法的实现
/// <summary>
/// 物体obj绕着自身的轴axis,进行旋转,旋转的速度为speed,当旋转的累计时间达到duration后,停止旋转
/// </summary>
/// <param name="obj">需要进行旋转的物体</param>
/// <param name="axis">旋转的轴向,应该是一个单位向量</param>
/// <param name="speed">旋转的速度,单位为度/秒</param>
/// <param name="duration">旋转的总时间,单位为秒</param>
/// <returns></returns>
public static async UniTask DoRotate(this GameObject obj, Vector3 axis, float speed, float duration, CancellationToken ctk,bool restore = true)
{
    try
    {
        float rotateTime = 0f;
        Quaternion startRotation = obj.transform.rotation; // 初始旋转角度

        bool isOver = false;

        // Update的内容:Unity 2020.2, C# 8.0 
        Func<UniTask> UpdateLoop = async () =>
        {
            //绑定到Update中去执行
            await foreach (var _ in UniTaskAsyncEnumerable.EveryUpdate())
            {
                if (rotateTime >= duration) break;
                if (ctk.IsCancellationRequested)
                {
                    throw new OperationCanceledException();
                    break;
                }

                float deltaTime = Time.deltaTime;
                float rotateAngle = speed * deltaTime; // 计算旋转角度
                obj.transform.Rotate(axis, rotateAngle, Space.Self); // 使用 Transform.Rotate 方法进行旋转                 
                rotateTime += deltaTime;                   
            }
            isOver = true;
            return;
        };

        UpdateLoop();
        await UniTask.WaitUntil(() => isOver == true);

        // 恢复初始旋转角度
        if (restore == true)
        { 
            obj.transform.rotation = startRotation; 
        }
    }
    catch (Exception e)
    {
        Debug.Log($"DoRotate报错:{e.Message}");
        Debug.Log($"    抛出一个OperationCanceledException");
        throw new OperationCanceledException();
    }
} 

五、思路扩展

(1)流程控制思考

如果是单线的顺序流程,则很方便,如果中间包含分支执行呢,那就借鉴Linq的WhenAll和WhenAny来实现。

using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;

/// <summary>
/// 等待所有的子步骤执行完毕
/// </summary>
public class WhenAll: MonoBehaviour,IFlowAsync
{
    /// <summary>
    /// 流程组
    /// </summary>
    public List<MonoBehaviour> scripts = new List<MonoBehaviour>();

    private string this_name;

    void Start()
    {
        LoadSteps();
        this_name = this.name;
    }

#if UNITY_EDITOR
    [ContextMenu("加载步骤")]
#endif
    void LoadSteps()
    {
        scripts.Clear();
        var root = this.gameObject.transform;
        int childCount = root.childCount;
        for (int i = 0; i < childCount; i++)
        {
            Transform childTransform = root.GetChild(i);

            //处理子物体
            Debug.Log(childTransform.name);

            //获取脚本,隐藏的不获取
            if (childTransform.gameObject.activeSelf)
            {
                var script = childTransform.GetComponent<MonoBehaviour>();
                scripts.Add(script);
            }
        }
    }

#if UNITY_EDITOR
    [ContextMenu("步骤编号")]
#endif
    async UniTask Test2()
    {
        int i = 0;
        foreach (var script in scripts)
        {
            var name = script.name;
            string newName = "";
            if (script.name.Contains("】"))
            {
                var nameRight = name.Split('】')[1];
                newName = $"【{i}{nameRight}";
            }
            else
            {
                newName = $"【{i}{name}";
            }
            script.name = newName;
            i++;
        }
    }

#if UNITY_EDITOR
    [ContextMenu("测试")]
#endif
    void Test()
    {
        var ctsInfo = TaskSingol.CreatCts();
        FlowAsync(ctsInfo.cts.Token);
    }

    /// <summary>
    /// 主步骤
    /// </summary>
    /// <param name="ctk"></param>
    /// <returns></returns>
    public async UniTask FlowAsync(CancellationToken ctk)
    {
        try
        {
            var allTasks = scripts.Select(s => (s as IFlowAsync).FlowAsync(ctk));
            await UniTask.WhenAll(allTasks).AttachExternalCancellation(ctk);
        }
        catch (Exception e)
        {
            Debug.Log($"{this_name}.Anim报错:{e.Message}");
            Debug.Log($"\n 抛出一个OperationCanceledException");
            throw new OperationCanceledException();
        }        
    }
}

(2)延时等待

public async UniTask FlowAsync(CancellationToken ctk)
{
    await UniTask.Delay(TimeSpan.FromSeconds(delayTimeInSeconds), cancellationToken: ctk);
}

(3)等待Animation播放结束

public async UniTask FlowAsync(CancellationToken ctk)
{
    myAnimation.enabled = true;

    if(order == "正序")
    {
        myAnimation[myAnimation.clip.name].time = 0;
        myAnimation[myAnimation.clip.name].speed = 1;            
    }
    else if (order == "倒序")
    {
        myAnimation[myAnimation.clip.name].time = myAnimation[myAnimation.clip.name].length;
        myAnimation[myAnimation.clip.name].speed = -1;
    }
    myAnimation.Play(myAnimation.clip.name);//播放动画

    var duration = myAnimation[myAnimation.clip.name].time;
    await UniTask.Delay(TimeSpan.FromSeconds(duration));
}

(4)等待一个或者多个button被点击(【全部点击】或者【点击任意一个】)

using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 功能介绍:异步流程——等待按钮被点击, 等待一个或者多个按钮被点击
/// 脚本参数:【All:全部】或者【Any:任意一个】被点击
/// </summary>
public class BottonOnClicked : MonoBehaviour,IFlowAsync
{
   /// <summary>
   /// 按钮组
   /// </summary>
   [Header("按钮组")]
   public List<Button> buttons = new List<Button>();

   /// <summary>
   /// 等待的类型【all | any】
   /// </summary>
   [Header("等待的类型【all | any】")]
   public string waitType;

   /// <summary>
   /// 点击后隐藏该button
   /// </summary>
   [Header("点击后隐藏该button")]
   public bool hideAfterClicked = false;

#if UNITY_EDITOR
   [ContextMenu("测试")]
#endif
   public async UniTask FlowAsync(CancellationToken ctk)
   {
       try 
       {
           buttons.ForEach(b => b.gameObject.SetActive(true));

           if (waitType == "all")
           {
               await UniTask.WhenAll(buttons.Select(b => b.OnClickAsync(ctk))).AttachExternalCancellation(ctk);
           }
           else if (waitType == "any")
           {
               await UniTask.WhenAny(buttons.Select(b => b.OnClickAsync(ctk))).AttachExternalCancellation(ctk);
           }
           else
           {
               Debug.LogError("BottonOnClicked中的waitType只能是【all】或者【any】");
           }

           //隐藏按钮?
           if (hideAfterClicked) buttons.ForEach(b => b.gameObject.SetActive(false));
       }
       catch (Exception e)
       {
           Debug.Log($"BottonOnClicked脚本报错:{e.Message}\n 抛出一个OperationCanceledException");
           throw new OperationCanceledException();
       }        
   }
}

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

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

相关文章

CleanMyMac2023永久版Mac清理工具

CleanMyMac拥有超强的清理能力&#xff0c;能够快速清理Mac垃圾文件、陈旧数据&#xff0c;恢复Mac原本性能&#xff0c;保持电脑好状态&#xff01;CleanMyMac中文版功能非常的强大&#xff0c;操作简单&#xff0c;使用起来高效快捷&#xff0c;自身拥有一个安全数据&#xf…

安装OpenCV依赖包libjasper-dev无法安装

记录在ubuntu22.04上编译OpenCV4.6&#xff0c;遇到依赖包无法安装的问题。 主要是源的问题 sudo gedit /etc/apt/sources.list我的源文件&#xff0c;######处的下一行是改动的地方&#xff0c;主要把自带的源都注释了&#xff0c;然后在末尾添加了清华源。 # deb cdrom:[Ubu…

详解C语言中的int8_t、uint8_t、int16_t、uint16_t、int32_t、uint32_t、int64_t、uint64_t

2023年8月8日&#xff0c;周二上午 目录 为什么会产生int8_t、uint8_t等这类数据类型int8_t、uint8_t等这类数据类型有什么用头文件int8_t、uint8_t等这类数据类型是怎么实现的 为什么会产生int8_t、uint8_t等这类数据类型 根本原因在于&#xff0c;C 语言标准只是规定了各个…

CentOS8装不上软件

App stream什么提示、或者找不到firewalld服务等类似提示时。可以尝试替换/etc/yum.repos.d文件夹下的文件内容。记得替换之前先备份原有的&#xff0c;防止意外情况便于恢复。 替换后文件如下&#xff1a; 文件下载地址&#xff1a; https://download.csdn.net/download/zhao…

也谈态势感知的嵌套与级联

不同颗粒度的态势感知可以嵌套在一起&#xff0c;形成一个层次结构&#xff0c;从而提供全面和多层次的信息获取和理解。 在态势感知中&#xff0c;颗粒度可以理解为观察、收集和分析信息的细节程度。较高颗粒度的态势感知关注的是具体的事件、行动或细节&#xff0c;提供了详细…

如何高性能、高效率地实现3D Web轻量化?

随着互联网和Web技术的发展&#xff0c;3D Web应用的需求越来越多。然而&#xff0c;复杂的3D模型在Web上展示和交互通常需要大量的带宽和计算资源。为了解决这一问题&#xff0c;HOOPS技术作为一套专业的3D图形技术开发工具包&#xff0c;发挥着关键作用。本文将探讨HOOPS技术…

问道管理:新创业板权限怎么开通?

在我国证券商场中&#xff0c;创业板能够被认为是相对较为年青的板块&#xff0c; 但在近几年中&#xff0c;创业板体现出了极高的投资价值。为了更好地促进我国立异企业开展&#xff0c;政府开放了新创业板权限。可是&#xff0c;对于很多人来说&#xff0c;新创业板权限是一个…

【MySQL学习】道士下山——MySQL的安装教程

学习数据库&#xff0c;不仅仅需要学习数据库的理论知识&#xff0c;还需要掌握一定的数据库开发能力&#xff1b;为后续的数据库应用开发、数据库内核开发等打下良好的基础。 对于学习者而言&#xff0c;目前市面上的Oracle等数据库软件需要付费&#xff0c;而MySQL作为一款免…

又一关键系统上线!海底捞进销存系统登陆OceanBase,数据库整体成本节省50%

2020 年&#xff0c;海底捞全面实现“云上捞”&#xff0c;将所有核心业务系统上云&#xff0c;全面推进企业数字化进程。而随着海底捞业务的不断增长&#xff0c;加上数智化转型构建全新的线上智慧服务&#xff0c;原有 IT 系统架构已经逐渐无法有效“驾驭”海量数据与突发流量…

Java之多线程和并发应用

多线程和并发应用 1. 概念与意义2. 多线程的实际应用场景2.1 网络通信2.2 数据库操作2.3 图片处理 3. 多线程的创建方式3.1 继承 Thread 类3.2 实现 Runnable 类3.3 实现 Callable 接口3.4 比较 Thread 和 Runnable3.5 线程同步与锁3.6 线程池 4. 并发编程的挑战和解决方案4.1 …

mysql存储过程定时调度

假设我们要创建一个简单的数据库&#xff0c;其中包含两张表&#xff1a;students 表和 courses 表&#xff0c;以及一个存储过程用于插入学生数据。下面是完整的建表语句、插入语句和存储过程&#xff1a; 1】建表 -- 创建 courses 表 CREATE TABLE courses (course_id INT …

【自然语言处理】大模型高效微调:PEFT 使用案例

文章目录 一、PEFT介绍二、PEFT 使用2.1 PeftConfig2.2 PeftModel2.3 保存和加载模型 三、PEFT支持任务3.1 Models support matrix3.1.1 Causal Language Modeling3.1.2 Conditional Generation3.1.3 Sequence Classification3.1.4 Token Classification3.1.5 Text-to-Image Ge…

OpenLayers入门,OpenLayers涂鸦手绘线条、圆形和多边形,涂鸦线条自动收尾连接成多边形

专栏目录: OpenLayers入门教程汇总目录 前言 本章再次讲解OpenLayers绘制图形功能,上一章中《OpenLayers图形绘制,OpenLayers实现在地图上绘制线段、圆形和多边形》我们已经讲过多种图形的绘制,本章主要讲解自由涂鸦手绘绘制线条,圆形和任意形状。 二、依赖和使用 &q…

Spring BeanPostProcessor 接口的作用和使用

BeanPostProcessor 接口是 Spring 框架中的一个扩展接口&#xff0c;用于在 Spring 容器实例化、配置和初始化 bean 的过程中提供自定义的扩展点。通过实现这个接口&#xff0c;您可以在 bean 实例创建的不同生命周期阶段插入自己的逻辑&#xff0c;从而实现对 bean 行为的定制…

微仓助力制造业数字化转型,实现高效智能供应链!

随着制造业数字化转型的浪潮迅猛涌现&#xff0c;为了应对日益复杂和快速变化的市场需求&#xff0c;制造企业面临着提高生产效率、降低成本、实现供应链高效运作的挑战。在这个数字化时代&#xff0c;微仓作为智能仓储解决方案&#xff0c;正发挥着关键的作用&#xff0c;成为…

verity cannot ... ‘/dev/block/dm-4‘ is read-only/ 证书cacerts系统目录

网上的说的一种做法是 su mount -o rw,remount / mount -o rw,remount /system cp /data/misc/user/0/cacerts-added/269953fb.0 /system/etc/security/cacerts/ rm /data/misc/user/0/cacerts-added/269953fb.0 reboot 但是我机子是android 12.0 提示 /dev/block/dm-4…

NKD:容器云集群与 OS 一体化运维利器

NKD 是 NestOS-kubernetes-Deployer 的缩写&#xff0c;是为了基于 NestOS 部署的 Kubernetes 集群运维工作准备的解决方案。其目标是在集群外提供对集群基础设施&#xff08;包括操作系统和 Kubernetes 基础组件&#xff09;的部署、更新和配置管理等服务。 1. 引言 Kuberne…

双栏排版中,[!b]等等的一些命令都不行啊

\usepackage{stfloats} Latex&#xff1a;图片、表格占据双栏排版的两栏时 的位置控制_latex stfloats_weixin_39450145的博客-CSDN博客目录1.问题&#xff1a;怎么在双栏排版中&#xff0c;让占据两栏的表格出现在页面顶端&#xff1f;2.解决&#xff1a;1&#xff09;图片&a…

378. 有序矩阵中第 K 小的元素

378. 有序矩阵中第 K 小的元素 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a;__378有序矩阵中第K小的元素__直接排序__378有序矩阵中第K小的元素__归并排序__378有序矩阵中第K小的元素__二分查找 原题链接&#xff1a; 378. 有序矩阵中…

中学高级本习集c++

第一章 回溯法 1.1 马拦过河卒 源程序名 knight.???&#xff08;pas, c, cpp&#xff09; 可执行文件名 knight.exe 输入文件名 knight.in 输出文件名 knight.out 【问题描述】 棋盘上A点有一个过河卒&#xff0c;需要走到目标B点…