javascript - 有没有比 setTimeout(0) 更快的方式让 Javascript 事件循环?

标签 javascript settimeout web-worker event-loop

我正在尝试编写一个执行可中断计算的网络 worker 。我知道的唯一方法(Worker.terminate() 除外)是定期屈服于消息循环,以便它可以检查是否有任何新消息。例如,这个 web worker 计算从 0 到 data 的整数之和。 ,但是如果您在计算过程中向它发送一条新消息,它将取消计算并开始新的计算。

let currentTask = {
  cancelled: false,
}

onmessage = event => {
  // Cancel the current task if there is one.
  currentTask.cancelled = true;

  // Make a new task (this takes advantage of objects being references in Javascript).
  currentTask = {
    cancelled: false,
  };
  performComputation(currentTask, event.data);
}

// Wait for setTimeout(0) to complete, so that the event loop can receive any pending messages.
function yieldToMacrotasks() {
  return new Promise((resolve) => setTimeout(resolve));
}

async function performComputation(task, data) {
  let total = 0;

  while (data !== 0) {
    // Do a little bit of computation.
    total += data;
    --data;

    // Yield to the event loop.
    await yieldToMacrotasks();

    // Check if this task has been superceded by another one.
    if (task.cancelled) {
      return;
    }
  }

  // Return the result.
  postMessage(total);
}

这行得通,但速度慢得惊人。 while 的平均每次迭代在我的机器上循环需要 4 毫秒!如果您想快速取消,这是一个相当大的开销。

为什么这么慢?有没有更快的方法来做到这一点?

最佳答案

是的,消息队列将比超时具有更高的重要性,因此会以更高的频率触发。

您可以使用 MessageChannel API 轻松绑定(bind)到该队列。 :

let i = 0;
let j = 0;
const channel = new MessageChannel();
channel.port1.onmessage = messageLoop;

function messageLoop() {
  i++;
  // loop
  channel.port2.postMessage("");
}
function timeoutLoop() {
  j++;
  setTimeout( timeoutLoop );
}

messageLoop();
timeoutLoop();

// just to log
requestAnimationFrame( display );
function display() {
  log.textContent = "message: " + i + '\n' +
                    "timeout: " + j;
  requestAnimationFrame( display );
}
<pre id="log"></pre>


现在,您可能还希望在每个事件循环中批处理几轮相同的操作。

以下是此方法有效的几个原因:
  • Per specs , setTimeout在第 5 级调用之后,即 OP 循环的第五次迭代之后,将被限制到至少 4 毫秒。
    消息事件不受此限制。
  • 部分浏览器会使setTimeout发起的任务在某些情况下,优先级较低。
    即,Firefox does that at page loading , 以便脚本调用 setTimeout此时不要阻止其他事件;他们甚至为此创建了一个任务队列。
    即使仍未指定,似乎至少在 Chrome 中,消息事件具有“用户可见”priority ,这意味着一些 UI 事件可以先出现,但仅此而已。 (使用 Chrome 中即将推出的 scheduler.postTask() API 对此进行了测试)
  • 大多数现代浏览器会在页面不可见时限制默认超时,而这 may even apply for Workers .
    消息事件不受此限制。
  • As found by OP , Chrome 确实为前 5 次调用设置了至少 1 毫秒。


  • 但是请记住,如果所有这些限制都已放在 setTimeout ,这是因为以这样的速度调度这么多任务是有成本的。

    仅在 Worker 线程中使用它!

    在 Window 上下文中执行此操作将限制浏览器必须处理的所有正常任务,但他们会认为这些任务不太重要,例如网络请求、垃圾收集等。
    此外,发布新任务意味着事件循环必须以高频率运行并且永远不会空闲,这意味着更多的能量消耗。

    关于javascript - 有没有比 setTimeout(0) 更快的方式让 Javascript 事件循环?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61338780/

    相关文章:

    javascript - 有没有办法找到鼠标指针半径?

    javascript - SetTimeout 后获取返回值

    javascript - setTimeout到底执行了多少次?

    javascript - 如何通过 importScripts 将 js 函数导入到 webWorker 中

    javascript - JS "for in"错误 IE7/8

    javascript - Angular Services and Scope - 为什么值(value)在其他范围内发生变化?

    javascript - 如何检查 URL 是否与当前页面和/或工作线程同源

    javascript - Service Worker 可以做什么 Web Worker 不能做的事情?

    php - 使用 AJAX 会增加 PHP 的内存使用吗?

    jquery - 如何通过 jQuery 中的 SetTimeout 函数传递元素 ID?