【问题标题】:How to run two operations at the same time using either web worker or event loop in javascript如何在 javascript 中使用 web worker 或事件循环同时运行两个操作
【发布时间】:2022-01-05 16:30:36
【问题描述】:

我想让Javascript同时运行两个操作。

我想让 javascript 在运行 interval 的同时处理一个大的 loop 来做一个计时器。

我在网上搜索,发现我们只有两种方法,要么使用event loop,要么使用web worker

我已经为此苦苦挣扎了很长时间,但我很清楚如何真正使用web workerevent loop

对于event loop,我无法真正理解它提供的文档的代码:

while (queue.waitForMessage()) {
  queue.processNextMessage()
}

queue 代表什么?如果我在代码中直接使用queue,它将显示queue is not defined

另外,我怎么能在我的代码中实际使用它?

对于web worker,这是我的代码:

    let result = document.getElementById('result')
    let time = document.getElementById('time')
    let worker;
    let script = document.getElementsByTagName('script')
      let interval = setInterval(function(){
        time.textContent = parseFloat(time.textContent)+1
      },1000)
    function display(){
    for(let i =0;i<10000;i++){
      result.innerHTML +=i
    }
    }
let startworker = function(){
   if(typeof(worker)=='undefined'){
      worker = new Worker(script);
      //I think this will produce error, but I don't know how to deal with the onmessage to let it run the function
      worker.onmessage = function(){
        display();
      }
   }
}
 

<div id='result'></div>
<button onclick='startworker()'>Click</button>
  <div id='time'>0</div>
如果我的代码,如果我点击button,它将产生failed to construct 'Worker'。另外,对于onmessage 部分,我真的不知道如何让它运行loop,我认为我的代码不正确。

我已经阅读了很多文章和文档,但我仍然不知道该怎么做。

谁能帮助我理解这两种方法并给我一个更好的方法来解决这种情况?

感谢您的任何回应并原谅我的无知!!!

例子:

let result = document.getElementById('result')
let time = document.getElementById('time')
let interval;
function start(){
   interval = setInterval(function(){
    time.textContent = parseFloat(time.textContent)+1
  },1000)
  setTimeout(reallystart(),0)
}
function reallystart(){
for(let i =0;i<10000;i++){
  if(i == 9999){
   window.clearInterval(interval)
  }
  result.innerHTML +=i
}
}
 

<button onclick='start()'>Click</button>
  <div id='result'></div>
  <div id='time'>0</div>

【问题讨论】:

  • 您提供的事件循环代码 sn-p 是浏览器代码的一部分,而不是您的。目的是说明事件处理是如何在幕后工作的。同时运行一堆间隔时间会看到添加到队列中和从队列中删除的事件。它还说明了为什么有些事情不会在您想要的时候发生 - 仍然有未处理的消息积压..
  • 我不知道你想做什么。并行运行意味着您同时运行它们,然后等待它们全部完成然后再次运行。无论如何,我认为您需要的是异步并行功能。如果您通过更好的示例提供有关您的代码的更多信息会更好地理解。
  • @pfndesign,我希望在处理函数时运行计时器
  • @jackssmith 它只是您每个流程的计时器的一个计时器吗?您可以运行一个间隔,然后检查进程是否正在使用变量运行,如果没有运行,则删除计时器
  • @jackssmith - 您能详细说明什么这两件事是您想同时做的吗?如果你真的需要同时做一些事情,使用 WebWorker 似乎是可行的方法,因为每个工作人员都有自己的线程。而使用消息队列使用单个线程来等待和处理消息 - 同时结束的两个超时将一个接一个地处理。

标签: javascript web-worker event-loop


【解决方案1】:

您不必自己编写事件循环,您将使用浏览器的。

不过,对于您同时运行(同步)循环和计划任务的情况,确实有两种方法:

  • 在单个线程上,通过在多个任务中中断循环。
  • 在多个线程上,通过使用 Web Workers。

浏览器中的事件循环最终可以非常粗略地概括为一个while 循环。在每次迭代中,它会选择一个新任务并运行它直到完成(实际上它会从多个任务源中选择任务,以便可以设置优先级系统)。
您的 JavaScript 作业本身通常作为此类任务的一部分执行*,并且在此 JavaScript 作业本身运行完成之前任务不会结束。这意味着如果您的 JavaScript 工作永远不会结束,事件循环将无法处理下一个任务,因为它会卡在生成您的代码的那个。
setInterval(和setTimeout)确实安排一个新任务在将来的某个时间执行传递的 JavaScript。但是再一次,当您的 for 循环阻塞了事件循环时,这个新计划的任务将永远无法完成它的工作。

为了解决这个问题,我们因此必须不阻塞事件循环以让它也选择新安排的任务。

要在单个事件循环中执行此操作,您必须将循环分成小块,并将每个块的执行分开在各自的任务中。这样,事件循环将能够确定它是否必须执行这些任务之外的其他任务:

let i = 0;
// for loop only updates i
const executeForLoop = async () => {
  for(; i < Infinity; i++) {
    // a chunk of 1000 loops
    if(i % 1000 === 0) {
      await waitNextTask();
    }
  }
};
executeForLoop();
// every 1s we update the displayed value
setInterval(() => {
  document.getElementById("log").textContent = i;
}, 1000);

// currently the fastest way to post a new task
// see https://stackoverflow.com/questions/61338780/
function waitNextTask() {
  return new Promise( (res) => {
    const channel = waitNextTask.channel ??= new MessageChannel();
    channel.port1.addEventListener("message", () => res(), { once: true }); 
    channel.port1.start();
    channel.port2.postMessage("");
  });
}
&lt;pre id="log"&gt;&lt;/pre&gt;

但是,这样做仍然会占用大量计算能力,而这些计算能力通常会影响到正确呈现页面和处理所有用户交互,因此只有在别无选择时才使用它,例如因为计算需要访问只能在 DOM 等主线程中访问的数据。

处理长时间运行的脚本的更好方法是使用所谓的“专用工作者”。这通常会在其他线程上运行,但无论如何都会有其自己的事件循环
所以你可以随心所欲地阻塞这个 Worker 的事件循环,而主线程将继续工作,就像什么都没发生一样。

但是,您需要在 Worker 中执行阻塞脚本worker.onmessage 处理程序仍将在主线程中执行,并且仍会阻塞主事件循环。

所以之前的例子重写为使用 Worker:

let i;
// every 1s we update the displayed value
setInterval(() => {
  document.getElementById("log").textContent = i;
}, 1000);
const worker = new Worker(getWorkerURL());
worker.onmessage = (evt) => {
  i = evt.data;
};

// because we can't link to external files in StackSnippets
// we use a dirty blob: URL hack
function getWorkerURL() {
  const elem = document.querySelector("[type='worker-script']");
  const content = elem.textContent;
  const blob = new Blob([content], { type: "text/javascript" });
  return URL.createObjectURL(blob);
}
<script type="worker-script">
// the hard computation is made in the script
// that will be used to initiate the Worker
let i = 0;
const executeForLoop = () => {
  for(; i < Infinity; i++) {
    // we keep a chunking logic
    // to avoid spamming the main thread with too many messages
    if(i % 1000 === 0) {
      postMessage(i);
    }
  }
};
executeForLoop();
</script>
<pre id="log"></pre>

*JS 作业也可以在update-the-rendering 步骤中作为回调调用,从技术上讲,它与任务无关,但对我们网络作者来说没有明显的区别。

【讨论】:

  • 感谢您的回答。我不确定这是否是我的问题,但在web worker 示例中,script type="worker-script" 不会被 js 引擎解析,因此Cannot read properties of null (reading 'textContent') at getWorkerURL 将产生null。我不确定为什么这在stackoverflow 中有效。我使用了 sublime 文本,即使我创建了一个新的 html 文件并直接复制您的代码,代码也不起作用
  • sn-p 的 html 部分需要在独立的 html 文件中的 javascript 之前设置。 StackOverflow 的 StackSnippets 进行交换。但是在您的情况下,您甚至不需要脚本的这一部分,直接使用外部文件,例如将其称为worker.js,将&lt;script type="worker-script"&gt;的内容粘贴到其中,删除function getWorkerURL() {并替换new Worker(getWorkerURL()); new Worker("worker.js");.
猜你喜欢
  • 2017-04-28
  • 1970-01-01
  • 2023-03-04
  • 2011-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多