非阻塞I/O的高速无限循环

7

有没有比 window.requestAnimationFrame() 更快的替代方案,用于不阻塞I/O的无限循环?

我在循环中所做的事情与动画无关,因此我不关心下一帧何时准备好,而且我已经了解到window.requestAnimationFrame()受监视器刷新率的限制,或者至少等待可以绘制一帧。

我也尝试了以下方法:

function myLoop() {
    // stuff in loop
    setTimeout(myLoop, 4);
}

数字4是因为这是setTimeout中的最小时间间隔,较小的值仍会默认为4。但是,我需要比这更好的分辨率。

有没有更好性能的解决方案?

基本上我需要一个非阻塞版本的while(true)


“(4是因为这是setTimeout中的最小间隔,较小的值仍将默认为4。)”这比那更复杂,并且至少已更改两次。只需使用0,并让实现担心是否要增加它。” - T.J. Crowder
@T.J. 我正在使用 Electron,因此我可以同时访问 node 模块和 window.blahblah :) - Joey
如果您需要某些东西运行的频率超过250次/秒,为什么不让它一次运行多个迭代,然后允许I/O,重复执行呢?如果您想要真正的亚四毫秒分辨率,我认为您正在错误的语言和操作系统上进行编程。 - ASDFGerte
2个回答

5
两件事情比setTimeout更快的是:
  • process.nextTick 回调(仅适用于NodeJS):

    process.nextTick() 方法会将回调添加到“下一个tick队列”中。一旦当前事件循环结束,所有目前在下一个tick队列中的回调都将被调用。

    这不是setTimeout(fn, 0)的简单别名。它更加高效。它会在事件循环下一次执行之前运行,包括定时器在内的任何其他I/O事件。

  • Promise状态变更通知

因此,这些可能成为你的工具,你可以将它们与setTimeout混合使用,以实现你想要的平衡。

细节如下:

正如你所知,给定的JavaScript线程基于任务队列运行(规范称其为作业队列)。你可能也知道,在浏览器中有一个默认的主UI线程,在NodeJS中运行单个线程。

但实际上,现代实现中至少存在两个任务队列:我们都知道的主要任务队列(其中setTimeout和事件处理程序放置它们的任务),以及“微任务”队列,其中某些异步操作在处理主任务(或“大任务”)期间被放置。这些微任务将在主任务完成后立即处理,下一个大任务之前处理,即使那个下一个大任务在微任务之前就被排队了。

nextTick回调和Promise状态变更通知都是微任务。因此,安排任何一个任务都会安排一个异步回调,但该回调将在下一个主任务之前发生。

我们可以通过setInterval和promise解决链在浏览器中查看这一点:

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">&nbsp;</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>

当你运行并点击“Hog”按钮时,请注意计数器显示会冻结,然后再次继续。这是因为它前面有300,000个微任务被调度了。还要注意我们写的三条日志消息上的时间戳(它们不会出现在片段控制台中,直到一个宏任务将它们显示出来,但时间戳可以告诉我们何时记录它们)。
基本上,你可以安排一堆微任务,并定期让它们运行完毕,然后运行下一个宏任务。
注意:我在片段中的浏览器示例中使用了setInterval,但是对于使用NodeJS进行类似实验的情况,setInterval可能不是一个好选择,因为NodeJS的setInterval与浏览器中的setInterval有些不同,并且具有一些令人惊讶的时间特性。

谢谢,虽然这不是我使用情况的正确解决方案,但我还是给了你一个勾选标记,因为它正确回答了问题。 - Joey

0

Cron 无法超越秒级分辨率。 - Darren Cook

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接