开发随笔——JavaScript定时器时延问题
Posted shi_zi_183
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了开发随笔——JavaScript定时器时延问题相关的知识,希望对你有一定的参考价值。
javascript定时器时延问题
问题描述
项目中,使用setInterval()创造三个定时器模拟时钟,一个时间间隔1秒,一个x秒(x取2,4,6),一个取10x毫秒。
理想中,第三个定时器调用100次后,第二个调用1次;实际中第二个调用一次,第三个仅能调用60+次,且第一个与第二个定时器在多次调用后不在同步。
问题原因
所有Javascript定时器让人头大的问题几乎都取决于JavaScript的单线程,因为是单线程的所以JavaScript没有真正意义的异步,所有的异步事件都需要在主线程中排队等待执行。
Interval定时器是在固定时间后将任务推入主线程的队列(队尾),Timeout定时器是立即将任务推入主线程队列,在任务执行时判断当前时间是否满足要求(当前时延是否大于设定时延),如果满足立即执行,如果不满足重新推入队中(队尾)。
这种设计就带来了三个问题
1、如果一个计时器被阻塞而不能立即执行,它将延迟执行直到下一次可能执行的时间点才被执行(比期望的时间间隔要长些),这种现象会在时间间隔短或者**重复次数多(延迟累积)**时非常明显。
2、如果setInterval回调函数的执行时间将足够长(比指定的时间间隔长),它们将连续执行并且彼此之间没有时间间隔。
3、如果setInterval回调函数的执行时间将足够长(比指定的时间间隔长),任务队列中待执行的任务会不断增加,可能会导致内存溢出。
本问题是第一条带来的,Interval定时器执行时间间隔比期望的长,导致固定时间中没有执行期望次数
解决思路
我们问题是固定时延并不能保证理想的时延,那我们可不可以通过动态的减少时间间隔来补偿等待时间或代码运行带来的时差。
为此我们放弃使用Interval定时器,重复使用Timeout定时器来模拟Interval。
即,Timeout定时器的任务结束时再新建一个Timeout定时器,这样可以动态的控制时间间隔。
代码
原代码
var timer=null;
run(){
......//项目代码
if(..//结束条件){
clearInterval(timer);
timer=null;
}
}
timer=setInterval(run,20);
优化代码
var timer=null;
var starttime=new Date().getTime();//获取开始时间
var counter3=0;
run(){
counter3++;//记录执行次数,用于计算时差
......//项目代码
if(..//结束条件){
timer=null;
}else{
let endtime=new Date().getTime();//获取任务结束时间
let ztime=endtime-starttime-counter3*20//使用实际结束时间-期望结束时间=时延偏差
let tmp=20-ztime//通过下次提前执行来补偿时差
timer=setTimeout(run,(tmp>0)?tmp:0);//防止tmp小于0时报错,若tmp小于零表示即使立即执行也无法补偿时差,我们只能使其立即,尽可能减少时差
}
}
timer=setTimeout(run,20);
测试结果
优化后,时差可以控制在5ms之内,且不在累积,效果显著。
注:这里之前尝试在新建Timeout定时器之前清除原有的Timeout定时器(来优化内存占用),发现会导致程序中断,即新的计时器新建失败,问题原因未知,故放弃该想法。
但测试发现即使不清除Timeout定时器,内存占用没有明显增加,也没有随时间占用增多的情况,姑且认为JavaScript拥有可靠的垃圾回收机制,不需要手动清理使用过的Timeout。
总结
Interval定时器因为JavaScript单线程带来了诸多问题。
建议使用循环Timeout来模拟定时任务。
好处有两点
1、Timeout定时器可以在每次调用时重新计算期望时延,作为补偿措施来控制时延过长的问题。
2、Timeout定时器模拟Interval后,在主线程中同一时间仅会有一个延时任务,避免了当任务执行时间大于时间间隔时,导致的主线程任务不断增加,进而内存溢出问题。
3、也避免了当任务执行时间大于时间间隔时,多个累积任务连续执行且没有时间间隔的问题(Timeout任务之间的时间间隔一定大于设定时间,不会因为累积而连续或缩短)。
以上是关于开发随笔——JavaScript定时器时延问题的主要内容,如果未能解决你的问题,请参考以下文章