如何编写不会阻止ui的Javascript代码

时间:2018-12-03 19:10:34

标签: javascript multithreading performance

我正在处理执行2个工作的javascript应用程序。

第一项工作更为重要,需要以60fps的速度运行。另一个作业是“后台”作业,仍然需要运行,但是可以花更长的时间。

通常,我这样做的方法是在RequestAnimationFrame循环中放置更重要的作业代码,然后将后台作业放在Web worker上。

但是主要工作已经产生了2位Web工作者,并且我不想由于上下文切换和内存消耗的原因而产生第3位。

RequestAnimationFrame循环上还有大约8毫秒的处理时间,我需要使用该处理时间来运行后台作业,但这是一项大约需要100毫秒才能完成的工作。

我的问题是,有没有一种方法可以编写一个循环,使每次UI即将被阻塞时都会暂停?

基本运行尽可能多的代码,直到帧剩余的8毫秒时间用完为止,然后暂停直到再次有空闲时间为止。

2 个答案:

答案 0 :(得分:1)

这是isn't well-supported yet的当前实验技术,但是:有requestIdleCallback

  

...排队在浏览器空闲期间要调用的函数。这使开发人员可以在主事件循环上执行后台和低优先级的工作,而不会影响诸如动画和输入响应之类的延迟关键事件。函数通常以先进先出的顺序调用;但是,如果有必要,可以将指定了超时的回调称为乱序,以便在超时之前运行它们。

rIC的关键问题之一是它收到了IdleDeadline object

  

...让您确定用户代理估计它将保持空闲状态的时间,以及属性didTimeout,该属性使您可以确定由于超时时间已到期,回调是否正在执行。

因此,当deadline.timeRemaining() method返回的剩余毫秒数足够少时,您可以使循环停止。


也就是说,我想我可能会添加第三个工作器,然后在尝试其他方法之前先在积极的测试中查看其外观。是的,的确是上下文切换成本很高,并且您不想过度使用它。另一方面,这些天来,移动设备和架构上已经有很多其他东西在进行上下文切换,而且速度很快。我不能说出移动设备上的工作人员的内存需求(我自己没有测量过),但这就是我要开始的地方。

答案 1 :(得分:0)

我推荐requestIdleCallback(),因为它是公认的答案,但它仍处于试验阶段,我喜欢提出这样的内容。您甚至可以将rIC与该答案结合使用,以生产出更适合您需求的产品。

第一个任务是将您的空闲代码分成多个可运行的小块,以便您可以检查块之间的时间/花费了多少时间。

一种方法是在队列中创建完成所需工作的多个函数,例如unprocessed.forEach(x=>workQueue.push(idleFunc.bind(null,x)));},然后让执行程序在某个时候将队列处理一定的时间。

如果您的循环需要一段时间才能完成,则可以使用generator function并在每个循环结束时让步,然后在您自己的截止日期或{的情况下,对setTimeout()的递归调用中运行它{1}}。

您还可以具有一个递归函数,该递归函数在处理后会将其自身添加回队列的末尾,这在您希望给其他工作时间运行时或者在为每件工作创建函数时会很荒谬的情况提供帮助(例如,绑定到一个函数的数百个数组项仅需要1ms的时间来处理)。

无论如何,这是我出于好奇而产生的。

requestIdleCallback()

如果您可以在代码中使用class IdleWorkExecutor { constructor() { this.workQueue=[]; this.running=null; } addWork(func) { this.workQueue.push(_=>func()); this.start(); } // addWorkPromise(func) { return new Promise(r=>{ this.workQueue.push(_=>r(func())); this.start(); }); //DRY alternative with more overhead: //return new Promise(r=>this.addWork(_=>r(func()))); } sleep(ms) { return new Promise(r=>setTimeout(r,ms)); } //Only run the work loop when there is work to be done start() { if (this.running) {return this.running;} return this.running=(async _=>{ //Create local reference to the queue and sleep for negligible performance gain... const {workQueue,sleep}=this; //Declare deadline as 0 to pause execution as soon as the loop is entered. let deadline=0; while (workQueue.length!==0) { if (performance.now()>deadline) { await sleep(10); deadline=performance.now()+1; } /*shift* off and execute a piece of work. *push and shift are used to create a FIFO buffer, but a growable ring buffer would be better. This was chosen over unshift and pop because expensive operations shouldn't be performed outside the idle executor.*/ workQueue.shift()(deadline); } this.running=false; })(); } } //Trying out the class. let executor=new IdleWorkExecutor(); executor.addWork(_=>console.log('Hello World!')); executor.addWorkPromise(_=>1+1).then(ans=>{ executor.addWork(_=>console.log('Answer: '+ans)); }); //A recursive busy loop function. executor.addWork(function a(counter=20) { const deadline=performance.now()+0.2; let i=0; while (performance.now()<deadline) {i++} console.log(deadline,i); if (counter>0) { executor.addWork(a.bind(null,counter-1)); } }); ,则将其添加到requestIdleCallback()非常简单:

IdleWorkExecutor