Unity 定时回调系统技术专题(Siki Plane)

官网Plane的GitHub
官网视频
官方B站视频
在多线程中,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。

101 物体的生命周期

//有无勾勾
在这里插入图片描述

FixedUpdate


    private void FixedUpdate()
    {
   	    public float timer = 0f;
    	public float time = 1f;
    	
        timer += Time.fixedDeltaTime;        

//fixedDeltaTime。下图是1秒1次输出
在这里插入图片描述

(问题) NullReferenceException: Object reference not set to an instance of an object

//一直存在,但不影响运行
//unityHub下载不了2020(下载到快完了,全部没了)
//所以从官网下安装包,但又找不到中文包,所以从unityHub下载的2018中文包复制过来
//不知道是不是这原因

102 FixedUpdate

不是固定步长递增

    private void FixedUpdate()
    {
        Test02();
    }
    void Test02()
    {
        print("启动时长:" + Time.realtimeSinceStartup);
        print("启动时长:" + Time.realtimeSinceStartupAsDouble);
        print("启动帧数:" + Time.renderedFrameCount);
    }

在这里插入图片描述

最大允许时间步进

驱动刚体运动时,超过最大允许时间步进,就终止此次运算,进行主循环运行,以此保证时间。
所以实际刚体运动会慢点,但忽略不计。
在这里插入图片描述

103 脚本执行顺序

需求

//一个物体下两个脚本的执行顺序
//一个物体下同一个脚本的执行顺序

不同脚本的执行顺序

//AB对比同一个物体下运行后,组件的顺序就固定了,后面再调整无用
//AC对比,同一物体下,组建运行顺序从下到上(不同物体也是从下到上)
在这里插入图片描述

Unity自带的脚本执行顺序(针对不同脚本)

在这里插入图片描述

不同物体下同一个脚本执行顺序

//第一次从下到上
//后面再调整物体顺序,不便了
在这里插入图片描述
//作者推荐的,用一个父节点来管理

    public Transform aTrans;
    public Transform bTrans;
    // Start is called before the first frame update
    void Start()
    {
        
        bTrans.GetComponent<NewBehaviourScript103_D_1>().D1();
        aTrans.GetComponent<NewBehaviourScript103_D_1>().D1();
    }

在这里插入图片描述

(问题) 学习时脚本分类

到网页复制标题
到VS利用Alt键盘修改标题(一般不允许加空格,如下下图是不行的)
MD、bat(另存为ASNI编码,默认的UTF8是会乱码)
“灵者更名”添加空格
在这里插入图片描述
//MD 新建文件夹
在这里插入图片描述

104 理解Unity主线程设计思想1

//一个线程,主线程,串行运行
//逻辑帧,一个物体从Update到下一次Update的时间

105 理解Unity主线程设计思想2(线程ID)

//不允许在主线程之外访问transform,限制编程环境单线程
//底层运用线程池,不需要开发者管理

    void Start()
    {
        ThreadStart threadStart = new ThreadStart(ThreadNew);
        Thread thread = new Thread(threadStart);
        thread.Start();

        print("主线程" + Thread.CurrentThread.ManagedThreadId);
    }
    void ThreadNew()
    {
        print("新线程" + Thread.CurrentThread.ManagedThreadId);
    }

在这里插入图片描述

106 协程的常规使用1(开启协程的两种方式的区别)

//第一种调用参数上限没有限制
//第一种调用参数上限为1

    void Start()
    {
        StartCoroutine(A(1,2));
        //StartCoroutine("A",1);
    }
  
    IEnumerator A(int a,int b)
    {
        print("前");
        yield return new WaitForSeconds(2f);
        print("后");
    }   

107 协程的常规使用2(终止协程)

问答
//我也测试到StopCoroutine(A()); 对 StartCoroutine(A()); 无效
//视频也讲到StopCoroutine(A()); 对 StartCoroutine(“A”); 无效

    [Tooltip("协程")] Coroutine coroutine;
    [Tooltip("迭代器")] IEnumerator enumerator;
    // Start is called before the first frame update
    void Start()
    {
        
    }
    IEnumerator A()
    {   
        print("前");
        yield return new WaitForSeconds(2f);
        print("后");  
    }
    // Update is called once per frame
    void Update()
    {
        StartA();
        StopA();
    }
    void StartA()
    {
        
        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            StartCoroutine(A()); print("开启");
        }
        else if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            StartCoroutine("A"); print("开启");
        }
        else if (Input.GetKeyDown(KeyCode.Alpha3))
        {
            coroutine = StartCoroutine(A()); print("开启");
        }
        else if (Input.GetKeyDown(KeyCode.Alpha4))
        {
            enumerator = A(); print("开启");
            StartCoroutine(enumerator);
        }
    }
    void StopA()
    {
        if (Input.GetKeyUp(KeyCode.Alpha1))
        {
            StopCoroutine(A()); print("结束");
        }
        else if (Input.GetKeyUp(KeyCode.Alpha2))
        {
            StopCoroutine("A"); print("结束");
        }
        else if (Input.GetKeyUp(KeyCode.Alpha3))
        {
            StopCoroutine(coroutine); print("结束");
        }
        else if (Input.GetKeyUp(KeyCode.Alpha4))
        {
            StopCoroutine(enumerator); print("结束");
        }
        else if (Input.GetKeyDown(KeyCode.Alpha5))
        {
            StopAllCoroutines(); print("结束");
        }
    }

108 深入理解协程原理1

事件函数的执行顺序
//协程的最大作用是加载资源

失效开启协程的物体

//开启协程后,失效物体,再次激活物体,协程不运行(Unity那张生命周期图,OnDisabled就没协程了)
在这里插入图片描述

协程串协程

//单线程的体现

    void Start()
    {
        StartCoroutine(A());
    }

    
    IEnumerator A()
    {
        print("1");
        yield return new WaitForSeconds(3f);
        print("2");
        yield return StartCoroutine(B());
        print("3");
    }
    IEnumerator B()
    {
        print("4");
        yield return new WaitForSeconds(2f);
        print("5");
    }

在这里插入图片描述

协程串协程串协程

//我也蒙这翻译,再串一段协程
在这里插入图片描述
//yield像一堵墙,执行顺序如下。方框处是整个协程彻底结束的时候
在这里插入图片描述

    void Start()
    {
        StartCoroutine(A());
    }

    
    IEnumerator A()
    {
        print("1");
        yield return new WaitForSeconds(3f);
        print("2");
        yield return StartCoroutine(B());
        print("3");
    }
    IEnumerator B()
    {
        print("4");
        yield return new WaitForSeconds(2f);
        print("5");
        yield return StartCoroutine(C());
        print("6");
    }
    IEnumerator C()
    {
        print("7");
        yield return new WaitForSeconds(2f);
        print("8");
    }

在这里插入图片描述

109 深入理解协程原理2(资源加载)

//异步加载Resources文件夹里的某一物体

    ResourceRequest resourceRequest;
    GameObject go;
    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(LoadResourcesAsync());
    }
    IEnumerator LoadResourcesAsync()
    {
        resourceRequest = Resources.LoadAsync<GameObject>("Cube");//类型,名字
        yield return resourceRequest;

        go = ( resourceRequest.asset) as GameObject;

        if (go != null)
        {
            Instantiate(go);
        }
        else
        {
            throw new System.Exception("异常");
        }
    }   
    // Update is called once per frame
    void Update()
    {
        if (resourceRequest != null && go != null)
        {
            print(resourceRequest.progress);
        }
    }

110 实现思路分析

//服务器定时任务多
//协程依赖MonoBehavior依赖于Unity,不能在服务器跑Unity。如下图
//借鉴携程是帧驱动的思想实现计时器
在这里插入图片描述

201 搭建测试环境

脚本TimerSys(单例),GameRoot

202 初始化脚本顺序

以往单例放在Awake

public class TimerSys : MonoBehaviour
{

    public static TimerSys _instance;

	void Awake()
    {
        _instance = this;
    }
    public void AddTimeTask()
    {
        print("定时任务");
    }
public class GameRoot : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        TimerSys._instance.AddTimeTask();
    }

现在封装成方法

//加一个按钮事件
//单例采用封装方法,一次控制顺序,防止单例后运行

public class GameRoot : MonoBehaviour
{
    TimerSys timerSys;
    // Start is called before the first frame update
    void Start()
    {
        timerSys = GetComponent<TimerSys>();
        timerSys.Init();
    }
    public void OnAddtimeTaskClick()
    {
        timerSys.AddTimeTask();
    }
public class TimerSys : MonoBehaviour
{

    public static TimerSys _instance;

	public void Init()
    {
        _instance = this;
    }
    public void AddTimeTask()
    {
        print("定时任务");
    }

203 基础定时功能实现

//之前“黑暗之光”时,我用委托做了定时器(用Time.deltaTime的)。有点类似
//方法复制那里如果不是需要加上系统运行时间,参数改为PETimeTask也不错
在这里插入图片描述

(问题) NullReferenceException: Object reference not set to an instance of an object

NullReferenceException: Object reference not set to an instance of an object
//空指针
//没有做初始化taskList = new List();

public class TimerSys : MonoBehaviour
{
    public List<PETimetask> taskList;
    public static TimerSys _instance;

	public void Init()
    {
        _instance = this;
        taskList = new List<PETimetask>();
    }

PETimetask


//任务数据类
using System;

public class PETimetask
{
    public Action callback;//要定时的任务
    public float destTime;//延时几秒
}

GameRoot

public class GameRoot : MonoBehaviour
{
    TimerSys timerSys;
    // Start is called before the first frame update
    void Start()
    {
        timerSys = GetComponent<TimerSys>();
        timerSys.Init();
    }
    public void OnAddtimeTaskClick()
    {
        timerSys.AddTimeTask(FuncA, 2f);
    }


    void FuncA()
    {
        print("FuncA");
    }
}

TimerSys

public class TimerSys : MonoBehaviour
{
    public List<PETimetask> taskList;
    public static TimerSys _instance;

	public void Init()
    {
        _instance = this;
        taskList = new List<PETimetask>();
    }
    public void AddTimeTask(Action callback, float destTime)
    {
        print("添加定时任务");
        PETimetask task = new PETimetask();
        float time = Time.realtimeSinceStartup + destTime;
        task.callback = callback;
        task.destTime = time;
        //
        taskList.Add(task);
    }
    // Update is called once per frame
    void Update()
    {
        if (taskList.Count <= 0) return;
        for (int i = 0; i < taskList.Count; i++)
        {
            PETimetask task = taskList[i];
            if (Time.realtimeSinceStartup < task.destTime)
            {
                continue;
            }
            else
            {
                if (task.callback != null)//这个判空的直觉我体会不到
                {
                    task.callback();
                }              
                taskList.Remove(task);
                i--;//移除List自动接上去,所以还需要从原索引
            }
        }
    }
}

效果

在这里插入图片描述

204 增加临时缓存列表

需求

//多线程定时
//增加缓存列表taskTmpList,避免加锁提高效率

代码

public class TimerSys : MonoBehaviour
{

    [Tooltip("定时任务列表")] public List<PETimetask> taskList;
    [Tooltip("缓存的定时任务列表")] public List<PETimetask> taskTmpList;
    public static TimerSys _instance;

	public void Init()
    {
        _instance = this;
        taskList = new List<PETimetask>();
        taskTmpList = new List<PETimetask>();     
    }

    #region 添加定时任务
    public void AddTimeTask(Action callback, float delay)//默认毫秒
    {
        PETimetask task = new PETimetask();
        float time = Time.realtimeSinceStartup+ delay;
        task.callback = callback;
        task.destTime = time;
        //
        taskTmpList.Add(task);
    }
    #endregion
    //
    /// <summary>
    /// 加载缓存的临时列表<para />
    /// </summary>
    void LoadTaskTmpList()
    {  
        for (int i = 0; i < taskTmpList.Count; i++)
        {
            taskList.Add(taskTmpList[i]);
        }
        taskTmpList.Clear();
    }
    /// <summary>
    /// 执行定时任务<para />
    /// </summary>  
    void RunTaskList()
    {
        if (taskList.Count <= 0) return;
        for (int i = 0; i < taskList.Count; i++)
        {
            PETimetask task = taskList[i];
            if (Time.realtimeSinceStartup < task.destTime)
            {
                continue;
            }
            else
            {
                if (task.callback != null)//这个判空的直觉我体会不到
                {
                    task.callback();
                }
                taskList.Remove(task);
                i--;//移除List自动接上去,所以还需要从原索引 
            }
        }
    }

    void Update()
    {
        LoadTaskTmpList();
        RunTaskList();  
    }

    
}

(需求) 注释方法,全局提示

C# 方法注释,让参数、返回结果可见,并且实现换行显示
//其实这时我需求只需要提示方法是干什么的就行了
在这里插入图片描述

205 增加时间单位设置功能

//Time.realtimeSinceStartu的单位是秒,所以毫秒*1000f
//GameRoot的调用相应调整

public class TimerSys : MonoBehaviour
{

    [Tooltip("定时任务列表")] public List<PETimetask> taskList;
    [Tooltip("缓存的定时任务列表")] public List<PETimetask> taskTmpList;
    public static TimerSys _instance;

	public void Init()
    {
        _instance = this;
        taskList = new List<PETimetask>();
        taskTmpList = new List<PETimetask>();     
    }

    #region 添加定时任务
    public void AddTimeTask(Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond)//默认毫秒
    {
        delay = UnitConversion(delay, unit);
        //
        PETimetask task = new PETimetask();
        float time = Time.realtimeSinceStartup * 1000f+ delay;
        task.callback = callback;
        task.destTime = time;
        //
        taskTmpList.Add(task);
    }
    /// <summary>
    /// 单位换算成毫秒<para />
    /// <param name="delay">数值<para /></param>
    /// <param name="unit">delay的时间单位<para /></param>
    /// <returns>返回true换算为毫秒的delay</returns>
    /// </summary>  
    float UnitConversion(float delay, PETimeUnit unit = PETimeUnit.MillSecond)//
    {
        switch (unit)
        {
            case PETimeUnit.MillSecond:  break;
            case PETimeUnit.Second: delay = delay * 1000f; break;
            case PETimeUnit.Minute: delay = delay * 1000f * 60f; break;
            case PETimeUnit.Hour: delay = delay * 1000f * 60f * 60f; break;
            case PETimeUnit.Day: delay = delay * 1000f * 60f * 60f * 24f; break;
            default: { throw new Exception("异常"); }
        }

        return delay;
    }
    #endregion
    //
    /// <summary>
    /// 加载缓存的临时列表<para />
    /// </summary>
    void LoadTaskTmpList()
    {  
        for (int i = 0; i < taskTmpList.Count; i++)
        {
            taskList.Add(taskTmpList[i]);
        }
        taskTmpList.Clear();
    }
    /// <summary>
    /// 执行定时任务<para />
    /// </summary>  
    void RunTaskList()
    {
        if (taskList.Count <= 0) return;
        for (int i = 0; i < taskList.Count; i++)
        {
            PETimetask task = taskList[i];
            if (Time.realtimeSinceStartup * 1000f < task.destTime)
            {
                continue;
            }
            else
            {
                if (task.callback != null)//这个判空的直觉我体会不到
                {
                    task.callback();
                }
                taskList.Remove(task);
                i--;//移除List自动接上去,所以还需要从原索引 
            }
        }
    }

    void Update()
    {
        LoadTaskTmpList();
        RunTaskList();  
    }   
}

206 增加任务循环功能

需求

//delay,执行完一次后,destTime+=delay
//count>1,执行后-1
//count0循环执行
//count
1,执行后可以移除该定时任务
//
//采用构造方法
//GameRoot传参调用3次

(代码) PETimetask

public class PETimetask
{
    public Action callback;//要定时的任务
    public float destTime;//延时到游戏时间结束
    public int count;//执行次数
    public float delay;//延时几秒

    public PETimetask(Action callback, float destTime, int count, float delay)
    {
        this.callback = callback;
        this.destTime = destTime;
        this.count = count;
        this.delay = delay;
    }
}

(代码) TimerSys

    void RunTaskList()
    {
        if (taskList.Count <= 0) return;
        for (int i = 0; i < taskList.Count; i++)
        {
            PETimetask task = taskList[i];
            if (Time.realtimeSinceStartup * 1000f < task.destTime)
            {
                continue;
            }
            else
            {
                if (task.callback != null)//我没有意识咋
                {
                    task.callback();
                }

                if (task.count == 1)
                {
                    taskList.Remove(task);
                    i--;//移除List自动接上去,所以还需要从原索引 
                }
                else
                {
                    if (task.count != 0)
                    {
                        task.count--;                   
                    }
                    //定义0==循环
                    task.destTime += task.delay;
                }
                ......
   public void AddTimeTask(Action callback,float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count=1)//默认毫秒
    {
        delay = UnitConversion(delay, unit);
        //       
        float time = Time.realtimeSinceStartup * 1000f+ delay;
        PETimetask task = new PETimetask(callback, time, count, delay);
        //
        taskTmpList.Add(task);
    }

(问题) 可选参数必须出现在所有必要参数之后

//将int count提到前面
//视频是int count=1,也是一个可选参数,所以不用提
在这里插入图片描述

207 生成定时任务全局ID

需求分析

//锁里面处理id
//处理超出id(int类型)范围
//int.MaxValue
//锁
//我用taskList[i].id来给id遍历。视频是新建了一个idList专门存储id。(我想尽量减少变量,可能以后有问题,现在找不到问题)
//移除或减少次数,对于idList,都要移除id

(代码) TimerSys

//调用是执行3次

    [Tooltip("定义锁")] private static readonly string obj="lock";
    [Tooltip("全局id,初始值经过方法后是0,所以-1")] public int id;
    //[Tooltip("id列表")] public List<int> idList;
    public static TimerSys _instance;

	public void Init()
    {
    	......
        //idList = new List<int>();
        id = -1;
    }
        public void AddTimeTask(Action callback,float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count=1)//默认毫秒
    {
		......
        int id = GenerateId();
        PETimetask task = new PETimetask(callback, time, count, delay, id);
        //
        taskTmpList.Add(task);
        //idList.Add(id);
    }
    /// <summary>
    /// 生成唯一索引id<para />
    /// </summary>  
    int GenerateId()
    { 
        lock(obj)//多线程显示唯一id就要锁
        {
            id++;

            while (true)
            {
                //超出int最大值
                if (id == int.MaxValue)
                {
                    id = 0;
                }
                //是否用过了, 
                bool isUsed = false;
                for (int i = 0; i < taskList.Count; i++)
                {
                    if (id == taskList[i].id)
                    {
                        isUsed = true;
                        break;
                    }
                }
                if (isUsed) id++;
                else break;
            }         
        }

        return id;
    }

在这里插入图片描述

208 增加任务删除功能

//根据id,遍历taskList和taskTmpList,悠久移除,返回true
/;/没采用idList,只用taskList代码简洁了一些

(代码) TimeSys

   /// <summary>
    /// 删除定时任务<para />
    /// </summary>
    public bool DeleteTimeTask(int id)
    {
        bool isExisted = false;

        for (int i = 0; i < taskList.Count; i++)
        {
            if (id == taskList[i].id)
            {
                isExisted = true;
                taskList.RemoveAt(i);
                break;
            }
        }


        for (int i = 0; i < taskTmpList.Count; i++)
        {
            if (id == taskTmpList[i].id)
            {
                isExisted = true;
                taskTmpList.RemoveAt(i);
                break;
            }
        }
        return isExisted;
    }

//调用是3次,下图在第二次进行删除定时任务
在这里插入图片描述

209 增加任务替换功能

(需求)

//遍历两个列表进行替换
//原方法是循环输出FuncA,新方法定为输出一次FuncB

(代码)

   public bool ReplaceTimeTask(int id,Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1)
    {
        delay = UnitConversion(delay, unit);
        //       
        float time = Time.realtimeSinceStartup * 1000f + delay;
        PETimetask task = new PETimetask(callback, time, count, delay, id);
        //

        //必在两个表之一
        bool isReplaced = false;
        for (int i = 0; i < taskList.Count; i++)
        {
            if (id == taskList[i].id)
            {
                isReplaced = true;
                taskList[i] = task;
                break;
            }
        }
        if (isReplaced == false)
        {
            for (int i = 0; i < taskTmpList.Count; i++)
            {
                if (id == taskTmpList[i].id)
                {
                    isReplaced = true;
                    taskTmpList[i] = task;
                    break;
                }
            }
        }

        return isReplaced;
    }

在这里插入图片描述

(问题) FuncB执行了两次

taskTmpList.Add(task);使其多运行了一次

 public bool ReplaceTimeTask(int id,Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1)
    {
        delay = UnitConversion(delay, unit);
        //       
        float time = Time.realtimeSinceStartup * 1000f + delay;
        PETimetask task = new PETimetask(callback, time, count, delay, id);
        //
       // taskTmpList.Add(task);

210 清理定时任务全局ID

(需求)

定义一个列表,存储移除的task的id
该列表不为空的,和idList做对比,有的话就移除掉idList里的id
由于我是指直接取taskList[i].id这种形式,没有涉及idList,所以不需要

定义帧的任务数据类和Sys
我是新建一个类FrameTimerSys,原来的命名为TimeTimerSys。视频将这两部分放一起

211 帧定时任务开发1

需求

//使用lambda表达式来简化调用的输出函数
//System.DateTime.Now,系统现在时间
//回调加捕捉异常

lambda

    public void OnAddTimeTaskClick()
    {
        id=timerSys.AddTimeTask( 
            ()=> 
            {
                print("FuncA,id:" + id);
                print(",时间:"+System.DateTime.Now); 
            },
            1000f, PETimeUnit.MillSecond,0);//0是循环
    }

回调加捕捉异常

 void RunTaskList()
    {
        if (taskList.Count <= 0) return;
        for (int i = 0; i < taskList.Count; i++)
        {
            PETimetask task = taskList[i];
            if (Time.realtimeSinceStartup * 1000f < task.destTime)
            {
                continue;
            }
            else
            {
                try
                {
                    if (task.callback != null)//我没有意识要检查非空
                    {
                        task.callback();
                    }
                }
                catch (Exception e)
                {
                    print(e.ToString());
                }
                ......

212 帧定时任务开发2;213 测试帧定时任务

需求

//视频用一个每帧递增的frameCounter来代替Time.renderedFrameCount。
//我将frameCounter在没有定时任务时置于0
//新建帧任务数据类
//视频将帧定时的方法跟时间定时,写在一个类,我拆了出来,虽然双方的一些Tool类型的方法一样

PEFrameTask

//帧任务数据类
public class PEFrameTask
{
    public Action callback;//要定时的任务
    public int destFrame;//有定时任务时,frameCounter+delay
    public int count;//执行次数
    public int delay;//延时几帧
    public int id;//索引

    public PEFrameTask(Action callback, int destFrame, int count, int delay, int id)
    {
        this.id = id;
        this.callback = callback;
        this.destFrame = destFrame;
        this.count = count;
        this.delay = delay;
    }
}

FrameTimerSys

public class FrameTimerSys : MonoBehaviour
{
   
    [Tooltip("定时任务列表")]  public List<PEFrameTask> taskList;
    [Tooltip("缓存的定时任务列表")] public List<PEFrameTask> taskTmpList;

    [Tooltip("定义锁")] private static readonly string obj="lock";
    [Tooltip("全局id,初始值经过方法后是0,所以-1")] public int id;

    [Tooltip("有定时任务时的纪元帧")] public int frameCounter = 0;
    //[Tooltip("id列表")] public List<int> idList;
    public static FrameTimerSys _instance;

	public void Init()
    {
        _instance = this;
        taskList = new List<PEFrameTask>();
        taskTmpList = new List<PEFrameTask>();
        //idList = new List<int>();
        id = -1;
    }

    #region 增删改
    public int AddTimerTask(Action callback,int delay, int count=1)//默认毫秒
    {
        //       
        int id = GenerateId();
        PEFrameTask task = new PEFrameTask(callback, frameCounter+delay, count, delay,id);
        //
        taskTmpList.Add(task);

        return id;
    }


    /// <summary>
    /// 删除定时任务<para />
    /// </summary>
    public bool DeleteTimerTask(int id)
    {
        bool isExisted = false;

        for (int i = 0; i < taskList.Count; i++)
        {
            if (id == taskList[i].id)
            {
                isExisted = true;
                taskList.RemoveAt(i);
                break;
            }
        }

        for (int i = 0; i < taskTmpList.Count; i++)
        {
            if (id == taskTmpList[i].id)
            {
                isExisted = true;
                taskTmpList.RemoveAt(i);
                break;
            }
        }
        return isExisted;
    }
 
    /// <summary>
    /// 替换定时任务<para />
    /// </summary>

    public bool ReplaceTimerTask(int id,Action callback, int delay, int count = 1)
    {
        //       
        PEFrameTask task = new PEFrameTask(callback, frameCounter+delay, count, delay, id);
        //

        //必在两个表之一
        bool isReplaced = false;
        for (int i = 0; i < taskList.Count; i++)
        {
            if (id == taskList[i].id)
            {
                isReplaced = true;
                taskList[i] = task;
                break;
            }
        }
        if (isReplaced == false)
        {
            for (int i = 0; i < taskTmpList.Count; i++)
            {
                if (id == taskTmpList[i].id)
                {
                    isReplaced = true;
                    taskTmpList[i] = task;
                    break;
                }
            }
        }

        return isReplaced;
    }
    #endregion

    //
    /// <summary>
    /// 加载缓存的临时列表<para />
    /// </summary>
    void LoadTaskTmpList()
    {
        if (taskTmpList.Count <= 0) return;//一直打印输出,所以return
        for (int i = 0; i < taskTmpList.Count; i++)
        {
            taskList.Add(taskTmpList[i]);
        }
        taskTmpList.Clear();
    }
    /// <summary>
    /// 执行定时任务<para />
    /// </summary>  
    void RunTaskList()
    {
        if (taskList.Count <= 0)
        {
            frameCounter=0;
            return;
        }
        frameCounter++;
        for (int i = 0; i < taskList.Count; i++)
        {
            PEFrameTask task = taskList[i];
            if (frameCounter < task.destFrame)
            {
                continue;
            }
            else
            {
                try
                {
                    if (task.callback != null)//我没有意识要检查非空
                    {
                        task.callback();
                    }
                }
                catch (Exception e)
                {
                    print(e.ToString());
                }


                if (task.count == 1)
                {
                    taskList.Remove(task);
                    i--;//移除List自动接上去,所以还需要从原索引 
                }
                else
                {
                    if (task.count != 0)
                    {
                        task.count--;
                        //idList.Remove(task.id);
                    }
                    else
                    {
                        //定义0==循环
                    }
                    task.destFrame += task.delay;
                }


            }
        }
    }

    void Update()
    {
        
        LoadTaskTmpList();
        RunTaskList();  
    }


    #region Tool
    /// <summary>
    /// 生成唯一索引id<para />
    /// </summary>  
    int GenerateId()
    {
        lock (obj)//多线程显示唯一id就要锁
        {
            id++;

            while (true)
            {
                //超出int最大值
                if (id == int.MaxValue)
                {
                    id = 0;
                }
                //是否用过了, 
                bool isUsed = false;
                for (int i = 0; i < taskList.Count; i++)
                {
                    if (id == taskList[i].id)
                    {
                        isUsed = true;
                        break;
                    }
                }
                if (isUsed) id++;
                else break;
            }
        }

        return id;
    }
    #endregion

GameRoot_Frame 调用测试

public class GameRoot_Frame : MonoBehaviour
{
    FrameTimerSys timerSys;

    [Tooltip("为了测试删除,替换")] public int id;
    // Start is called before the first frame update
    void Start()
    {
        timerSys = GetComponent<FrameTimerSys>();
        timerSys.Init();
    }
    public void OnAddTimerTaskClick()
    {
        id=timerSys.AddTimerTask( 
            ()=> 
            {
                print("FuncA,id:" +id + " " + "帧数:"+Time.renderedFrameCount); 
            },
            60, 0);//0是循环
    }
    public void OnDeleteTimerTaskClick()
    {
        timerSys.DeleteTimerTask(id);
    }
    public void OnReplaceTimerTaskClick()
    {
        bool isReplaced=timerSys.ReplaceTimerTask(id,()=> { print("FuncB"); }, 60,  1);
        if (isReplaced) { print("替换成功!"); }
    }

}

效果

在这里插入图片描述

301 剥离Monobehaviour依赖

需求

//从这开始是服务器上的定时器,官网学员反应难度跟前面两部分对比明显(我也感受到了,没有一个视频敲一些出一个效果的步步前进)
//服务器不能装Unity,所以不能用using UnityEngine。去掉using UnityEngine;后Debug,print,Time,Update等都不能用了。所以要解决这几点。
在这里插入图片描述
//GameRoot,UI按钮调用
//TimerSys看,实例并且引用PETime的方法
//PETimer,去除using UnityEngine;和MonoBehaviour,放在服务器上。移植了前两个TimeSys的主体功能。重写update,打印,时间等原来依赖于Unity的部分

增加 删除 更新

GameRoot

public class GameRoot : MonoBehaviour
{
    TimerSys timerSys;
    [Tooltip("为了测试删除")] public int id;
    // Start is called before the first frame update
    void Start()
    {
        timerSys = GetComponent<TimerSys>();
        timerSys.Init();
    }

    #region 时间
    public void OnAddTimeTaskClick()
    {
        id = timerSys.AddTimeTask(
            () =>
            {
                print("FuncA,id:" + id);
                print(",时间:" + System.DateTime.Now);
            },
            1000f, PETimeUnit.MillSecond, 0);//0是循环
    }
    public void OnDeleteTimeTaskClick()
    {
        timerSys.DeleteTimeTask(id);
    }
    public void OnReplaceTimeTaskClick()
    {
        bool isReplaced = timerSys.ReplaceTimeTask(id, () => print("FuncB"), 1000f, PETimeUnit.MillSecond, 1);
        if (isReplaced) { print("替换成功!"); }
    }
    #endregion
    #region
    public void OnAddFrameTaskClick()
    {
        id = timerSys.AddFrameTask(
            () =>
            {
                print("FuncA,id:" + id);
                print(",时间:" + System.DateTime.Now);
            },
            60, 0);//0是循环
    }
    public void OnDeleteFrameTaskClick()
    {
        timerSys.DeleteFrameTask(id);
    }
    public void OnReplaceFrameTaskClick()
    {
        bool isReplaced = timerSys.ReplaceFrameTask(id, () => { print("FuncB"); }, 60,  1);
        if (isReplaced) { print("帧替换成功!"); }
    }
    #endregion
}

TimerSys

public class TimerSys :MonoBehaviour
{
    public static TimerSys _instance;
    public PETimer pt;

    public void Init()
    {
        _instance = this;

        pt=new PETimer();
        pt.Init();
        pt.SetLog((string log)=> { print("初始化"+log); });
    }

    #region 定时任务 增 删 改
    public int AddTimeTask(Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1)//默认毫秒
    {
        return pt.AddTimeTask(callback, delay, unit, count);
    }

    public bool DeleteTimeTask(int id)
    {
        return pt.DeleteTimeTask(id);
    }
    public bool ReplaceTimeTask(int id, Action callback, float delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1)
    {
        return pt.ReplaceTimeTask(id,callback, delay, unit, count);
    }
    #endregion
    
    #region 帧 增 删 改
    public int AddFrameTask(Action callback, int delay, int count = 1)//默认毫秒
    {
        return pt.AddFrameTask(callback,delay,count);
    }
    public bool DeleteFrameTask(int id)
    {
        return pt.DeleteFrameTask(id);
    }
    public bool ReplaceFrameTask(int id, Action callback, int delay, int count = 1)
    {
        return pt.ReplaceFrameTask(id, callback, delay, count);
    }
    #endregion
   
    private void Update()
    {
        pt.Update();
    }
}

302 设置日志处理

需求

//实现打印,update(没有继承MonoBehavior,需要TimerSys来驱动), 构造

PETimer的打印

//SetLog被外界调用,传入一个方法引用给自己的委托log
//Log规定自己的委托将会执行带字符串参数的方法体
在这里插入图片描述
// PETimer

  private Action<string> log;
  public void SetLog(Action<String> log)
    {
        if (log != null)
        {
            this.log = log;
        }     
    }
    private void Log(string log)
    {
        this.log(log);
    }

//TimerSys

    public void Init()
    {
    	......
        pt=new PETimer();
        pt.Init();
        pt.SetLog((string log)=> { print("初始化"+log); });
    }

PETimer的Update(靠其他脚本的Update来驱动)

// PETimer

    public void Update()
    {
        LoadTaskTmpList();
        RunTaskList();

        Frame_LoadTaskTmpList();
        Frame_RunTaskList();
    }

//TimerSys

    private void Update()
    {
        pt.Update();
    }

PETimer的构造

//我加上这一段报空

    public PETimer()
    {
        taskList.Clear();
        taskTmpList.Clear();

        frame_taskList.Clear();
        frame_taskTmpList.Clear();
    }

303 计算机纪年与UTC时区

    //当年出现C语言版本的Unix,32位的int按秒计算是68.1年。两个因素所以选1970
    public DateTime startDateTime = new DateTime ( 1970, 1, 1, 0, 0, 0,0 );
    public double GetUTCMillSeconds()//计算时间间隔
    {
        /**
            DateTime.Now本地时间,中国东八区
            DateTime.UtcNow时间标准时间
            实际服务器标准时间,到具体国家在进行本地偏移
        **/
        TimeSpan timeSpan = DateTime.UtcNow - startDateTime;
        return timeSpan.TotalMilliseconds;
    
    }

304 剥离UnityEngine的时间计算依赖

//将destTime和delay的类型改为double
//PETimer中将原本的 Time.realtimeSinceStartup改为GetUTCMillSeconds()
//视频定义了一个全局的nowTime=GetUTCMillSeconds();

PETimer的代码结构

具体的看Plane的github
官网Plane的GitHub PlaneZhong/PETimer
在这里插入图片描述

305 移植到控制台工程环境

需求

//VS 新建项目【控制台】添加到原方案
//拖过来PETimer,PETimeTask
在这里插入图片描述

Program.cs

using System;

namespace TimedCallback_VS
{
    class Program
    {
        static void Main(string[] args)
        {
            Test();
            Console.WriteLine("Hello World!");
            Console.ReadKey();
        }

        static void Test()
        {
            PETimer pt = new PETimer();
            pt.Init();

            pt.SetLog((string log) => { Console.WriteLine(log); });
            pt.AddTimeTask( ()=> { Console.WriteLine(pt.id+" "+DateTime.Now); }, 1000d,PETimeUnit.MillSecond,0);
            while (true)
            {
                pt.Update();
            }
        }
    }
}
 

在这里插入图片描述

306 Timer线程计时器

Thread.CurrentThread.ManagedThreadId线程Id

        static void ThreadTest()//那个线程空闲就用哪个线程
        {
            double millSeconds = 50d;
            System.Timers.Timer t = new System.Timers.Timer(millSeconds);//单独Timers有二义性
            t.AutoReset = true;

            t.Elapsed += (Object sendeer, ElapsedEventArgs args) =>
            {
                Console.WriteLine("线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
            };

            t.Start();
        }

在这里插入图片描述

307 整合Timer线程计器

program

        static void DetachedThread()//独立线程
        {
            PETimer pt = new PETimer(50);

            pt.SetLog((string log) => { Console.WriteLine(log); });
         
            pt.AddTimeTask(
                () => 
                { 
                    Console.WriteLine("任务id:{0},线程Id:{1}",pt.id,Thread.CurrentThread.ManagedThreadId.ToString()); 
                },
                100d, //执行AddTimeTask的时间间隔
                PETimeUnit.MillSecond,
                0
            );
        }

PETimer

    public PETimer(int interval=0)//
    {
        Init();//发现报空错误,是未初始化
        //
        taskList.Clear();
        taskTmpList.Clear();

        frame_taskList.Clear();
        frame_taskTmpList.Clear();
        //
        if (interval >= 0)
        {
            DetachedThread(interval);
        }
        
    }
    void DetachedThread(int interval)
    {
        System.Timers.Timer t = new System.Timers.Timer(interval);//执行Update时间间隔
        t.AutoReset = true;

        t.Elapsed += (Object sender, ElapsedEventArgs args) =>
        {
            Update();
        };

        t.Start();
    }

效果

在这里插入图片描述

308 增加任务回调tid参数

需求

将任务数据类(时间和帧)的callback类型改为public Action callback;。并对相关引用(根据报错)进行修改

PETimer

	public System.Timers.Timer serverTimer;
	
    public PETimer(int interval=0)//interval默认时间间隔
    {
        Init();//发现报空错误,是未初始化
        //
        taskList.Clear();
        taskTmpList.Clear();

        frame_taskList.Clear();
        frame_taskTmpList.Clear();
        //
        if (interval >= 0)
        {
            DetachedThread(interval);
        }
        
    }
    void DetachedThread(int interval)
    {
        serverTimer = new System.Timers.Timer(interval);//执行Update时间间隔
        serverTimer.AutoReset = true;

        serverTimer.Elapsed += (Object sender, ElapsedEventArgs args) =>
        {
            Update();
        };

        serverTimer.Start();
    }
    void Reset()//重启服务器
    {
        id = 0;

        taskList.Clear();
        taskTmpList.Clear();

        frame_taskList.Clear();
        frame_taskTmpList.Clear();

        log = null;
        serverTimer.Stop();
    }

Program

        static void DetachedThread()//独立线程
        {
            PETimer pt = new PETimer(50);

            pt.SetLog((string log) => { Console.WriteLine(log); });
         
            pt.AddTimeTask(
                (int id) => 
                { 
                    Console.WriteLine("任务id:{0},线程Id:{1}",pt.id,Thread.CurrentThread.ManagedThreadId.ToString()); 
                },
                100d, //执行AddTimeTask的时间间隔
                PETimeUnit.MillSecond,
                0
            

309 增加任务Handle设置功能

需求

307的的线程Id是变化的,
1、主体在主线程执行,一些文件I/O分流到独立线程,然后再回到主线程
2、逻辑部分也是多线程,数据修改部分加锁(性能高,开发麻烦,死锁)

如果要采用第一种方式。写任务柄
在Program定义任务包,实现任务柄的入队出队(加锁)

PETimer

修改两次(时间和帧,以下只显示时间的)运行

	public Action<Action<int> ,int > taskHandle;//回调,id
    public void SetHandle(Action<Action<int>, int> handle)
    {
        this.taskHandle = handle;
    }
    void RunTaskList()
    {
        	......
            else
            {
                Action<int> callback = task.callback;
                try
                {
                    if (taskHandle != null)
                    {
                        taskHandle(callback, task.id);
                    }
                    else if (task.callback != null)//我没有意识要检查非空
                    {
                        task.callback(task.id);
                    }
                }
            ......

Program

        private static readonly string obj="lock";

        static void DetachedThreadBackMainThread()//独立线程
        {
            Queue<TaskPack> taskPackQuene = new Queue<TaskPack>();
            PETimer pt = new PETimer(50);

            pt.SetLog((string log) => { Console.WriteLine(log); });

            pt.AddTimeTask(
                (int id) =>
                {
                    Console.WriteLine("任务id:{0},线程Id:{1}", pt.id, Thread.CurrentThread.ManagedThreadId.ToString());
                },
                100d, //执行AddTimeTask的时间间隔
                PETimeUnit.MillSecond,
                0
            );
            //对立面存在很多等待执行的任务
            pt.SetHandle( (Action<int> callback, int id)=>
            {
                if (callback != null)
                {
                    lock (obj)
                    {
                        taskPackQuene.Enqueue(new TaskPack(id, callback));
                    }                  
                }
            });
            //执行
            while (true)
            {
                if (taskPackQuene.Count > 0)
                {
                    TaskPack taskPack;
                    lock (obj)
                    {
                        taskPack = taskPackQuene.Dequeue();
                    }
                       
                    taskPack.callback(taskPack.id);
                }
            }
        }
   }
}
 
//任务包
class TaskPack
{
    public int id;
    public Action<int> callback;
    
    public TaskPack(int id, Action<int> callback )
    {
        this.callback = callback;
        this.id = id;
    }
}

效果

在这里插入图片描述

310 增加一些常用API(时间)

PETime

   #region ToolByTime

    ///<summary>获取当前时间<para /></summary>
    public double GetUTCMillSeconds()//计算时间间隔
    {
        /**
            DateTime.Now本地时间,中国东八区
            DateTime.UtcNow时间标准时间
            实际服务器标准时间,到具体国家在进行本地偏移
        **/
        TimeSpan timeSpan = DateTime.UtcNow - startDateTime;
        return timeSpan.TotalMilliseconds;
    }
    /// <summary>本地时间<para /></summary>
    public double GetMillSecondsTime()
    {
        nowTime= GetUTCMillSeconds();
        return nowTime;
    }
    /// <summary>本地时间<para /></summary>    
    public DateTime GetDateTime()
    {
        //方法一、异常卡断不会一直运行
        DateTime dateTime = TimeZone.CurrentTimeZone.ToLocalTime(startDateTime.AddMilliseconds( nowTime));
        
        //方法二、DateTime.Now;异常卡断会一直运行

        return dateTime;
    }
    public int GetYear()
    {
        return GetDateTime().Year;
    }
    public int GetMonth()
    {
        return GetDateTime().Month;
    }
    public int GetWeek()
    {
        return (int)GetDateTime().DayOfWeek;
    }
    public int GetDay()
    {
        return GetDateTime().Day;
    }
    public string GetLocalTimeString()
    {
        DateTime dateTime = GetDateTime();
        string dateTimeString = GetTimeString(dateTime.Hour)+",";
        dateTimeString += GetTimeString(dateTime.Minute) + ",";
        dateTimeString += GetTimeString(dateTime.Second);

        return dateTimeString;
    }
    public string GetTimeString(int time)
    {
        if (time == 0)
            return "0" + time.ToString();
        else
            return time.ToString();
    }

    #endregion

Program

        static void TimeTest()
        {
            PETimer pt = new PETimer(50);
            Console.WriteLine(pt.GetUTCMillSeconds().ToString());
            Console.WriteLine(pt.GetMillSecondsTime().ToString());
            Console.WriteLine(pt.GetDateTime().ToString());
            Console.WriteLine(pt.GetYear().ToString());
            Console.WriteLine(pt.GetMonth().ToString());
            Console.WriteLine(pt.GetWeek().ToString());
            Console.WriteLine(pt.GetDay().ToString());
            Console.WriteLine("\n");
        }

效果

//当时我没有定义double nowTime,直接用GetUTCMillSeconds(),第二条发生溢栈错误。重新定义了一个nowTime,没报错,如下图。

//报溢栈的代码,但是当我定义一次nowTime后再改回去,错误没有出现了
//

    public double GetUTCMillSeconds()//计算时间间隔
    {
        /**
            DateTime.Now本地时间,中国东八区
            DateTime.UtcNow时间标准时间
            实际服务器标准时间,到具体国家在进行本地偏移
        **/
        TimeSpan timeSpan = DateTime.UtcNow - startDateTime;
        return timeSpan.TotalMilliseconds;
    }
    /// <summary>本地时间<para /></summary>
    public double GetMillSecondsTime()
    {
        return GetUTCMillSeconds();
    }
    /// <summary>本地时间<para /></summary>    
    public DateTime GetDateTime()
    {
        //方法一、异常卡断不会一直运行
        DateTime dateTime = TimeZone.CurrentTimeZone.ToLocalTime(startDateTime.AddMilliseconds(GetMillSecondsTime()));
        
        //方法二、DateTime.Now;异常卡断会一直运行

        return dateTime;
    }

在这里插入图片描述

311 多线程数据安全处理1 锁Add和加载,锁id

临时列表timeTmpList的添加和清除是多线程的
1、加锁,性能低
2、锁在一个临时列表。临时列表的Add频率较低

id的我一开始就没有设id列表。视频是把Add放在生成id的最后面。在回收的时候锁id

Time

LoadTaskTmpList()的频率比AddTimeTask高,
LoadTaskTmpList()通过if及时return,规避锁
//
Frame的也如上草最

    private static readonly string lockTime = "lockTime";
    private static readonly string lockFrame = "lockTime";
    public int AddTimeTask(Action<int> callback, double delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1)//默认毫秒
    {

        delay = UnitConversion(delay, unit);
        //
        //int id = GenerateId();
        PETimeTask task = new PETimeTask(callback, GetUTCMillSeconds()+delay, count, delay, id);
        //
        taskTmpList.Add(task);

        return id;
    }
    void LoadTaskTmpList()
    {
        if (taskTmpList.Count <= 0) return;//一直打印输出,所以return

        for (int i = 0; i < taskTmpList.Count; i++)
        {
            taskList.Add(taskTmpList[i]);
        }
        taskTmpList.Clear();
    }

修改后

    public int AddTimeTask(Action<int> callback, double delay, PETimeUnit unit = PETimeUnit.MillSecond, int count = 1)//默认毫秒
    {

        delay = UnitConversion(delay, unit);
        //
        //int id = GenerateId();
        PETimeTask task = new PETimeTask(callback, GetUTCMillSeconds()+delay, count, delay, id);
        //
        lock (lockTime)
        {
            taskTmpList.Add(task);
        }
        
        return id;
    }
    /// <summary>加载缓存的临时列表<para /></summary>
    void LoadTaskTmpList()
    {
        if (taskTmpList.Count <= 0) return;//一直打印输出,所以return
        lock (lockTime)
        {
            for (int i = 0; i < taskTmpList.Count; i++)
            {
                taskList.Add(taskTmpList[i]);
            }
            taskTmpList.Clear();
        }

    }

锁id

在这里插入图片描述

312 多线程数据安全处理2 锁时间Delete

//有困惑的地方是for删除临时列表时,移除时,要不要j–;,解决移除后后面索引的前移

    private List<int> taskDeleteTmpList; 
    public void DeleteTimeTask(int id)
    {
        lock (lockTime)
        {
            taskDeleteTmpList.Add(id);
            Log("事件的删除临时列表的线程id:"+Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }
    public void DeleteTimeTask()
    {
        if (taskDeleteTmpList.Count > 0)
        {
            lock (lockTime)
            {
                for (int j = 0; j < taskDeleteTmpList.Count ; j++)
                {
                    bool isDeleted = false;

                    for (int i = 0; i < taskList.Count; i++)
                    {
                        if (id == taskList[i].id)
                        {
                            isDeleted = true;
                            taskDeleteTmpList.RemoveAt(j);j--;
                            taskList.RemoveAt(i);
                            break;
                        }
                    }
                    if (isDeleted)
                    {
                        continue;
                    }
                    else
                    {
                        for (int i = 0; i < taskTmpList.Count; i++)
                        {
                            if (id == taskTmpList[i].id)
                            {
                                taskDeleteTmpList.RemoveAt(j);j--;
                                taskTmpList.RemoveAt(i);
                                break;
                            }
                        }
                    }
                }

                taskDeleteTmpList.Clear();              
            }
        }
    }
     public void Update()
    {
		......
        DeleteTimeTask();
        ......
        

313 多线程数据安全处理3 锁帧Delete 锁帧Id

跟上面一样的操作
但也是没有视频的idList

    public void DeleteFrameTask(int id)
    {
        lock (lockFrame)
        {
            frame_taskDeleteTmpList.Add(id);
            Log("事件的删除临时列表的线程id:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }
    private void DeleteFrameTask()
    {
        if (frame_taskDeleteTmpList.Count > 0)
        {
            lock (lockFrame)
            {
                for (int j = 0; j < frame_taskDeleteTmpList.Count; j++)
                {
                    bool isDeleted = false;
                    for (int i = 0; i < frame_taskList.Count; i++)
                    {
                        if (id == frame_taskList[i].id)
                        {
                            isDeleted = true;
                            frame_taskDeleteTmpList.RemoveAt(j);
                            frame_taskList.RemoveAt(i);
                            break;
                        }
                    }
                    if (isDeleted)
                    {
                        continue;
                    }
                    else
                    {
                        for (int i = 0; i < frame_taskTmpList.Count; i++)
                        {
                            if (id == taskTmpList[i].id)
                            {
                                frame_taskDeleteTmpList.RemoveAt(j);
                                frame_taskTmpList.RemoveAt(i);
                                break;
                            }
                        }
                    }

                }
                frame_taskDeleteTmpList.Clear();
            }
        }
    }

314 多线程数据安全处理4 测试Delete 整合成一个文件

//input=Console.ReadLine(),所以要按d后快速回车

Program

       static void FinalTest()
        {
            Queue<TaskPack> taskPackQuene = new Queue<TaskPack>();
            PETimer pt = new PETimer(50);

            pt.SetLog((string log) => { Console.WriteLine(log); });

            int id=pt.AddTimeTask(
                (int id) =>
                {
                    Console.WriteLine("任务id:{0},线程Id:{1}", pt.id, Thread.CurrentThread.ManagedThreadId.ToString());
                },
                1000d, //执行AddTimeTask的时间间隔
                PETimeUnit.MillSecond,
                0
            );
          
            //执行
            while (true)
            {              
                pt.Update();
                Console.WriteLine("while中的id"+id);
                string input = Console.ReadLine();
                if (input == "d")
                {
                    pt.DeleteTimeTask(id);
                    Console.WriteLine("删除");
                }
                if (taskPackQuene.Count > 0)
                {
                    TaskPack taskPack;
                    lock (obj)
                    {
                        taskPack = taskPackQuene.Dequeue();
                    }

                    taskPack.callback(taskPack.id);
                }
            }
        }

效果

在这里插入图片描述

Frame

相应修改
在这里插入图片描述

315 课程总结回顾

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值