为什么worker的onmessage会在宏任务之后执行?

问题描述 投票:0回答:1

事件循环执行顺序

我正在研究 Javascript 事件循环如何工作以供将来的演示使用,但根据目前掌握的信息,我遇到了意外的行为。简单来说,当我向worker发送消息时,postmessage的执行总是发生在setTimeout之后..

脚本.js:

new Worker('worker.js').postMessage('');

setTimeout( () => {
    const now = new Date().toISOString();
    console.log(`setTimeout execution: ${now}`);
}, 0);

worker.js:

onmessage = function (event) {
    const now = new Date().toISOString();
    console.log(`Worker execution: ${now}`); 
}

如果你想自己在控制台尝试一下:

const workerCode = `
self.onmessage = function (event) {
  const now = new Date().toISOString();
  self.postMessage("worker executed:     " + now);
}`;

const blob = new Blob([workerCode], { type: 'application/javascript' });
const workerURL = URL.createObjectURL(blob);

const worker = new Worker(workerURL);

worker.onmessage = function (event) {console.log(event.data)};
worker.postMessage('');


setTimeout( () => {
    const now = new Date().toISOString();
    console.log(`setTimeout executed: ${now}`);
}, 0);

我的问题

如果我错了,请纠正我,但事件循环按以下顺序工作:

  1. 执行调用堆栈中的所有任务
  2. 执行微任务中的所有任务
  3. 在宏任务中执行任务
  4. 渲染图形/UI

如果确实如此,知道setTimeout是放在宏任务队列中的,也就是字面意思是最后一个读取的队列,那为什么postmessage会在aftersetTimeout之后执行呢?如果在同步执行期间,在setTimeout之前将其放入队列(无论该队列是什么)?

我已经尝试过什么?

我尝试延迟,但即使 setTimeout 延迟,这种情况仍然会发生,所以我相信造成这种情况的原因并不是工作线程在与主线程并行的线程中运行。示例:

setTimeout( () => {
    const start = performance.now();
    while (performance.now() - start < 1000) { };
    const now = new Date().toISOString();
    console.log(`Execution setTimeout: ${now}`);
}, 0);
javascript multithreading web-worker event-loop task-queue
1个回答
0
投票

如果我错了,请纠正我,但事件循环按以下顺序工作:

  1. 执行调用堆栈中的所有任务
  2. 执行微任务中的所有任务
  3. 在宏任务中执行任务
  4. 渲染图形/UI

不完全是这样,尽管这对于以下内容来说并不重要。

首先,将调用堆栈上的内容称为“任务”是相当混乱的。根据规范命名法,任务是一个规范构造,告诉浏览器如何执行某些操作。 调用堆栈上有 JS 脚本执行状态(函数、领域、作用域等)。
但是,是的,一旦调用堆栈为空,即当所有 JS 从上到下执行完时,就会执行微任务检查点。 请注意,每个task可能会发生多次,并且某些任务可能不执行任何JS。所以tasks和微任务之间没有直接关系。唯一的关系是它们都是告诉浏览器执行某些操作的规范构造。

现在,渲染是作为“更新渲染”特殊任务的一部分执行的。请注意,很长一段时间以来,它一直被认为是事件循环的特殊部分,直到最近我们才将其更新为实际任务,就像大多数浏览器实际实现的那样。 这个特殊任务负责运行一些回调(这将全部触发微任务检查点),然后将继续进行实际渲染。它通常在任务优先级系统中具有最高优先级之一,尽管没有真正指定。


知道
setTimeout

是放在宏任务队列中的,也就是字面意思是最后一个读取的队列,为什么

postMessage
会在
setTimeout
之后执行呢?如果在同步执行期间,它在
setTimeout
 之前被放入队列(无论该队列是什么)

尽管事件循环中有很多队列,但仍然只有一个事件循环(每个上下文)。因此,即使有优先级系统,每个队列的优先级也没有指定,浏览器可以自由选择他们想要执行的任何任务,只要它是自己排队的最旧的任务
task-source

(请注意,任务源不是任务队列,多个任务源可能会出现在同一个队列中)。 每个MessagePort
实际上都有自己的

自己的任务源
,并且setTimeout任务在
计时器任务源
上排队。同样,它们的优先级没有指定,并且通常具有相同的优先级,这对应于传入的 优先任务调度 API 中的
"user-visible"
,因此从 setTimeout(fn, 0) 排队的任务和消息事件之间的顺序未设置且无法假设。浏览器可以在这里自由地做任何他们想做的事。


但无论如何,这甚至不是我们现在所处的情况。

在您的情况下,您正在生成一个新的 Worker,它将有

自己的事件循环

您缺少的是,虽然 Worker
对象是同步可用的,但实际的工作线程却不是。浏览器首先必须获取脚本(异步),然后它必须生成一个新的上下文(异步),一旦它启动并运行,它将检查是否收到来自主线程的传入消息,最后执行这些消息。

所以你所看到的完全有道理,你只是在观察生成 Worker 的异步本质。


Ps:(请注意,由于我们有

OffscreenCanvas,至少 Chrome 会锁定 Worker 的执行,直到生成它的主线程结束当前任务,这是因为它们需要在两个上下文之间同步渲染)。


    
	

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.