【Unity】时钟定时任务系统的笔记

时钟定时任务系统是根据时间(毫秒、秒、分、时、天)为单位进行延时处理事件Action,Action是Unity自带无返回值委托类型。

1、定义时钟任务数据结构,一般需要延时时间(毫秒为单位)、目标时间(毫秒)、事件(Action)、执行次数(count)、唯一ID(tid)

其中,唯一ID的作用是让系统能够找出相应的定时任务进行处理,如删除,修改。

2、添加定时任务方法,需要传递一个事件Action,延时时间数值,执行次数,时间单位(枚举),根据以上信息可创建一个定时任务对象,其中延时时间和目标时间都需根据对应的时间单位进行转化为毫秒级别,tid是一个int从0开始递增的数值,必须保证其唯一性要用一个tidList列表来存放已经使用的tid,进行唯一性判断,为防止溢出还要进行tid > int.MaxValue安全校验 若溢出要归零。其中,由于定时任务创建后需要保存入一个字典,key是tid,value是定时任务对象,而这个字典在Update方法有删除操作,涉及到资源共享不能同时进行操作,即在某一帧时刻在Update正在使用字典,那么在Add方法里就不能操作字典,所以要创建一个临时字典来保存某一帧添加的定时任务,在下一帧后Update方法里将临时字典的内容放入真正的定时字典中,再进行对真正的定时字典进行处理操作。

3、删除定时任务方法,需要传递一个tid任务唯一ID,根据它来寻找任务,而由于添加定时任务的那一帧是用临时字典来保存的,而没放在真正字典里,需要对临时字典也进行遍历搜索是否有任务,若有的话进行移除即可,否则去真正的定时字典进行删除。

4、替换定时任务方法,传递一个tid,以及传递一个事件Action,延时时间数值,执行次数,时间单位(枚举),tid用于找出要替换的任务,后面那些参数是创建一个定时任务所需的,创建出后先从临时字典找,若找到直接替换即可,否则去真正的定时字典进行替换。

5、在Update方法中,将临时字典的定时任务放入真正的定时字典中,清空临时字典,接着对真正的定时字典进行遍历检查每个任务的是否到达目标时间,Time.realtimeSinceStartup * 1000 得到当前时间戳的毫秒级别,若大于等于任务的目标时间(毫秒),就算到达执行时间,执行任务事件Action并将目标时间加上延时时间得到下一次的目标时间,执行后判断执行次数count是否为1,【若为1,说明定时任务结束,保存其tid,在tidList中移除该tid,再遍历结束后进行遍历要删除的tid列表从真正的定时字典移除已经结束的任务】,否则count减一;

由于检查定时任务操作是非常繁琐的,优化是放入线程进行处理检查,处理方法如下:

        //开启线程检查定时任务 30毫秒检查一次
        System.Timers.Timer timer = new System.Timers.Timer(30);
        timer.AutoReset = true;//自动重置时间(必须设置为true)
        timer.Elapsed += (object sender, ElapsedEventArgs e) =>
        {
            timerSystem.CheckTask();//检查定时任务的方法
        };
        timer.Start();  //开启线程检查  

由于放入线程进行处理就涉及到一个问题,即Unity不允许在多线程对Unity相关物体如一些组件,物体的位置移动等进行操作,必须要在主线程即Unity的生命周期函数里面才允许,故定时任务需传回Unity生命周期函数如:Update进行执行,那么需要给定时任务系统一个委托,来将定时任务Action传递回去,如下:

  timerSystem.SetAddToMainThreadQueue((Action action) => {
            lock (obj)
            {
                //将定时任务存入队列
                mainQueue.Enqueue(action);
            }
        });

注意,以上方法timerSystem.SetAddToMainThreadQueue是定时任务系统的一个方法,括号是一个匿名方法,参数是一个Action即回来的定时任务,lock锁的作用是防止资源争夺问题,即同一资源不能被多个地方占用。

接着就可以在主线程即Update方法里进行执行那些传递回来的定时任务了。

    private void Update()
    {
        lock (obj)
        {
            while (mainQueue.Count > 0)
            {
                Action callBack = mainQueue.Dequeue();
                if(callBack != null)
                {
                    callBack();
                }
            }
        }
    }

重点说明下,lock的作用,lock(obj)是当obj没被锁住时,就会锁住obj,直到lock(){ ... }执行完毕,才会释放obj, 在这段时间若有其他线程也进入这里会因为obj被锁住而无法执行,而被挂起,直至obj被释放,该线程才可继续执行。

扫描二维码关注公众号,回复: 5974836 查看本文章

6、由于可考虑到添加定时任务 以及删除、替换等操作也可以在多线程进行处理,故涉及到资源争夺问题的都需要为每个资源加上一个锁,如临时字典加个lock(tempDic),真正字典加个lock(realyDic),tidList列表也要加个,凡是可能会出现同一时刻占用同一资源的情况就要考虑加锁了。

问题:

一、不考虑线程情况,添加定时任务和检查定时任务会在同一时刻发生吗?按理说是顺序执行的,要么是Add先,之后再执行Update检查,如果不会在同一时刻发生,就不需要所谓的临时字典来保存了,直接一个字典保存即可。

经过测试,居然是!!完全没有影响的呵呵,真的完全没必要创个临时字典来保存。。

猜你喜欢

转载自blog.csdn.net/qq_39574690/article/details/88533131