問題描述
對于不阻塞 I/O 的無限循環(huán),是否有比 window.requestAnimationFrame()
更快的替代方法?
Is there a faster alternative to window.requestAnimationFrame()
for endless loops that don't block I/O?
我在循環(huán)中所做的與動(dòng)畫無關(guān),所以我不在乎下一幀何時(shí)準(zhǔn)備好,并且我讀過 window.requestAnimationFrame()
的上限為顯示器的刷新率或至少等到可以繪制幀.
What I'm doing in the loop isn't related to animation so I don't care when the next frame is ready, and I have read that window.requestAnimationFrame()
is capped by the monitor's refresh rate or at least waits until a frame can be drawn.
我也嘗試了以下方法:
function myLoop() {
// stuff in loop
setTimeout(myLoop, 4);
}
(4 是因?yàn)檫@是 setTimeout
中的最小間隔,較小的值仍將默認(rèn)為 4.)但是,我需要比這更好的分辨率.
(The 4 is because that is the minimum interval in setTimeout
and smaller values will still default to 4.) However, I need better resolution than this.
還有什么性能更好的嗎?
Is there anything with even better performance out there?
我基本上需要 while(true)
的非阻塞版本.
I basically need a non-blocking version of while(true)
.
推薦答案
兩個(gè)比那個(gè)setTimeout
運(yùn)行得更快的東西:
Two things that will run sooner than that setTimeout
:
process.nextTick
回調(diào)(NodeJS 特定):
process.nextTick
callbacks (NodeJS-specific):
process.nextTick()
方法將回調(diào)添加到下一個(gè)滴答隊(duì)列".一旦事件循環(huán)的當(dāng)前輪次運(yùn)行完成,當(dāng)前在下一個(gè)滴答隊(duì)列中的所有回調(diào)都會(huì)被調(diào)用.
The
process.nextTick()
method adds the callback to the "next tick queue". Once the current turn of the event loop turn runs to completion, all callbacks currently in the next tick queue will be called.
這不是 setTimeout(fn, 0)
的簡單別名.它效率更高.它在事件循環(huán)的后續(xù)滴答中觸發(fā)任何其他 I/O 事件(包括計(jì)時(shí)器)之前運(yùn)行.
This is not a simple alias to setTimeout(fn, 0)
. It is much more efficient. It runs before any additional I/O events (including timers) fire in subsequent ticks of the event loop.
承諾結(jié)算通知
Promise settlement notifications
因此,這些可能是您工具帶的工具,可以將其中一個(gè)或兩個(gè)與 setTimeout
混合使用以達(dá)到您想要的平衡.
So those might be a tools for your toolbelt, doing a mix of one or both of those with setTimeout
to achieve the balance you want.
詳情:
您可能知道,給定的 JavaScript 線程基于任務(wù)隊(duì)列(規(guī)范稱其為作業(yè)隊(duì)列)運(yùn)行;您可能知道,瀏覽器中有一個(gè)主要的默認(rèn) UI 線程,而 NodeJS 運(yùn)行一個(gè)線程.
As you probably know, a given JavaScript thread runs on the basis of a task queue (the spec calls it a job queue); and as you probably know, there's one main default UI thread in browsers and NodeJS runs a single thread.
但事實(shí)上,現(xiàn)代實(shí)現(xiàn)中至少有兩個(gè)任務(wù)隊(duì)列:我們都認(rèn)為的主要任務(wù)隊(duì)列(setTimeout
和事件處理程序放置任務(wù)的地方),以及微任務(wù)"隊(duì)列,在主任務(wù)(或宏任務(wù)")的處理過程中放置??了某些異步操作.一旦宏任務(wù)完成,那些微任務(wù)就會(huì)被處理,在主隊(duì)列中的下一個(gè)宏任務(wù)之前 —即使下一個(gè)宏任務(wù)在微任務(wù)之前排隊(duì).
But in fact, there are at least two task queues in modern implementations: The main one we all think of (where setTimeout
and event handlers put their tasks), and the "microtask" queue where certain async operations are placed during the processing of a main task (or "macrotask"). Those microtasks are processed as soon as the macrotask completes, before the next macrotask in the main queue — even if that next macrotask was queued before the microtasks were.
nextTick
回調(diào)和承諾結(jié)算通知都是微任務(wù).因此,調(diào)度任一調(diào)度異步回調(diào),但將在下一個(gè)主要任務(wù)之前發(fā)生.
nextTick
callbacks and promise settlement notifications are both microtasks. So scheduling either schedules an async callback, but one which will happen before the next main task.
我們可以在帶有 setInterval
和 promise 解析鏈的瀏覽器中看到:
We can see that in the browser with setInterval
and a promise resolution chain:
let counter = 0;
// setInterval schedules macrotasks
let timer = setInterval(() => {
$("#ticker").text(++counter);
}, 100);
// Interrupt it
$("#hog").on("click", function() {
let x = 300000;
// Queue a single microtask at the start
Promise.resolve().then(() => console.log(Date.now(), "Begin"));
// `next` schedules a 300k microtasks (promise settlement
// notifications), which jump ahead of the next task in the main
// task queue; then we add one at the end to say we're done
next().then(() => console.log(Date.now(), "End"));
function next() {
if (--x > 0) {
if (x === 150000) {
// In the middle; queue one in the middle
Promise.resolve().then(function() {
console.log(Date.now(), "Middle");
});
}
return Promise.resolve().then(next);
} else {
return 0;
}
}
});
$("#stop").on("click", function() {
clearInterval(timer);
});
<div id="ticker"> </div>
<div><input id="stop" type="button" value="Stop"></div>
<div><input id="hog" type="button" value="Hog"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
當(dāng)您運(yùn)行它并單擊 Hog 按鈕時(shí),請注意計(jì)數(shù)器顯示是如何凍結(jié)的,然后又會(huì)繼續(xù)顯示.這是因?yàn)樘崆鞍才帕?300,000 個(gè)微任務(wù).還要注意我們編寫的三個(gè)日志消息的時(shí)間戳(它們不會(huì)出現(xiàn)在代碼段控制臺(tái)中,直到宏任務(wù)顯示它們,但時(shí)間戳?xí)@示它們何時(shí)被記錄).
When you run that and click the Hog button, note how the counter display freezes, then keeps going again. That's because of the 300,000 microtasks that get scheduled ahead of it. Also note the timestamps on the three log messages we write (they don't appear in the snippet console until a macrotask displays them, but the timestamps show us when they were logged).
所以基本上,您可以安排一堆微任務(wù),并定期讓這些微任務(wù)用完并運(yùn)行下一個(gè)宏任務(wù).
So basically, you could schedule a bunch of microtasks, and periodically let those run out and run the next macrotask.
注意:我在片段中的瀏覽器示例中使用了 setInterval
,但具體而言,setInterval
可能不是一個(gè)好的選擇對于使用 NodeJS 進(jìn)行的類似實(shí)驗(yàn),因?yàn)?NodeJS 的 setInterval
與瀏覽器中的有點(diǎn)不同,并且具有一些令人驚訝的時(shí)序特征.
Note: I've used setInterval
for the browser example in the snippet, but setInterval
, specifically, may not be a good choice for a similar experiment using NodeJS, as NodeJS's setInterval
is a bit different from the one in browsers and has some surprising timing characteristics.
這篇關(guān)于非常快的無限循環(huán),不會(huì)阻塞 I/O的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網(wǎng)!