使用延时替代定时器

时间:2022-05-22 23:31:29

这里分享的内容使用的是 js 语言,在其它语言也具有一样的原理。

在实现定时执行某个任务时,有两种实现思路。

1. 

hInterval = setInterval(function(){
    task()
}, 1000)

// 如欲停止执行:
clearInterval(hInterval)

2.

hTimeout = setTimeout(function f(){
   task()
   hTimeout = setTimeout(f, 1000)  // 通过延时递归实现反复执行
}, 100)

这两种做法严格的说是不相等的,后者由于 task() 执行需要耗费不等的时间,所以不存在稳定的间隔,前者可以保障稳定的间隔。

尽管如此,这两种做法都适用于实际需求场景。

但在实际编程时,由于切入点更多,后面的做法明显优于前者。

setTimeout(function f(){
   task()
   if(xxx) setTimeout(f, 1000)  
   // else 则不再执行,通过前置条件可以在内部控制是否执行
   setTimeout(f, another delay /*可以灵活换间隔*/) 
   setTimeout(f2 /* 可以更换为执行另一个任务 */, 1000) 
}, 100)

我们看到,

1. 添加一个前置的 if(cond) 就可以控制在满足特定条件时才反复执行。如使用定时器,则变为 if(){task()} else { 空转 },难以让定时器真正停下来。

2. (根据 task 的结果,)可以在下一轮换成其它的延迟间隔。当然,对于定时器来说,也可以在代码内部设置间隔。

3. 可以更换下一轮的任务。

对于使用定时器遇到的常见的场景,开关定时器,使用 setTimeout 也更容易。

function f(){
   task()
   setTimeout(f, 1000)
}

// 启动反复执行
f()

// 停止反复执行
clearTimeout(h)
// 但一般都会在 f 内部停止,很少由外部停止

 

可以看到,启动定时任务和执行一个普通函数没有差别。

另外,在网络编程中,网络任务耗时很难确定,定时器容易造成上一个任务未完成就启动下一个任务,引起并发问题,使用延迟方案则始终在一根线跑,也化解了潜在的并发陷阱。

在其它语言里也有类似的设计,如 java 里有 Executor.schedule(runnable) 和 Executor.scheduleWithInterval() 两个函数,其语义和 setTimeout setInterval 对等。