【问题标题】:Nodejs async loop is blocking React UI renderNodejs 异步循环阻塞了 React UI 渲染
【发布时间】:2020-12-17 15:06:15
【问题描述】:

我有一个电子应用程序,我需要通过 UDP 发送大量数据。这个过程是通过点击我的 React UI 中的一个按钮开始的,这个例子简化了我的问题。我的反应按钮:

<button
  onClick={() => {
    foo();
  }}
>
  Run foo
</button>;

Foo函数:

export const foo = async () => {
  while (true) {
    console.log("hello");
  }
};

虽然 foo 是异步的,但它仍然会冻结 React UI!

【问题讨论】:

  • JavaScript 在单线程中运行。即使函数是异步的,如果你让它运行一个无限的while循环,它仍然会阻塞应用程序。
  • 异步函数只是一个返回承诺的函数。它没有说明您在函数中或在 Promise 中所做的事情是异步的。你写了一个自旋循环,不管它在哪里运行都会阻塞线程。
  • async function(或一般的承诺)不会让事情在不同的线程中运行。为此,您需要使用工作人员。
  • 是的@Bergi,我一直在考虑使用工人,因为这可以让我创建一个新线程,这将是我使用任何其他语言的解决方案。 Nodejs 工作人员让我感到困惑......并不是说整个 nodejs 不会让我感到困惑
  • 您能否分享更多关于“我需要通过 UDP 发送大量数据”的详细信息(可能还有代码)?通常,解决方案不是在同步while(true) 循环中执行此操作,而是将它们分批成块,以延迟散布它们,和/或使用异步操作系统功能等待网络缓冲区实际发送数据包.

标签: javascript node.js reactjs asynchronous electron


【解决方案1】:

由于 JavaScript 是单线程的,一旦你的无限 while 循环异步函数被调用,它就会阻塞你的线程。您可以通过在 while 循环中引入 await 来解决此问题,这将允许您的程序返回执行代码的其他部分。

export const foo = async () => {
  while (true) {
    await new Promise(resolve => {
      resolve();
    });
  }
};

【讨论】:

  • 使用foo 的示例定义仍然会阻止用户界面。
【解决方案2】:

async 函数在执行时仍会阻塞主(也是唯一的)线程; async 函数通常有帮助,因为它们调用库函数实际上释放了主线程。

例如,如果您不执行简单的无限循环,而是执行以下操作:

function timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

function yieldThread() {
  return new Promise(resolve => resolve());
}

export const foo = async () => {
  while (true) {
    console.log("hello");
    await timeout(2000);
    //or
    await yieldThread();
  }
};

您的主线程不会被阻塞。

当然timeout 或简单的yield 通常不是处理这种情况的正确方法,但它应该是典型结构的例证。

假设你有一个函数sendUdp(packet, callback),它的第一个参数是数据包 您需要在完成发送后发送一个不带参数的回调作为第二个回调, 你可以这样做:

export const foo = async () => {
  while (true) {
    const packet = getPacket();
    await new Promise(resolve => {
        sendUdp(packet, resolve);
    });
  }
};

【讨论】:

  • yieldThread尝试你的例子,仍然会冻结用户界面。
  • @asd12tgzxvbgt 它完全冻结了吗?例如,如果您尝试使用超时示例,它是否有效?在这种情况下,只是您发出了很多请求并且使主线程超载,您应该对请求进行速率限制。请注意,在 node 中,API 支持同时发送多个数据包
  • 是的,完全冻结了。有趣的是,我创建了一个名为foo 的函数,它记录控制台然后调用setTimeout(foo, 0);。这在没有冻结 UI 的情况下工作了一段时间。后来,我将我的代码实现为foo,但它又冻结了。我删除了越来越多的代码来查找问题,但是当我回到使用 setTimeout(foo, 0) 的示例时,它仍然冻结了!这不是疯了吗?
  • @asd12tgzxvbgt 如果没有手动尝试的代码,我猜你正在淹没事件循环;虽然从技术上讲您确实没有阻塞主线程,但您仍然将几乎所有时间都花在 foo 函数上,并且 UI 冻结。您应该添加一些延迟;例如,在您的测试中,不是setTimeout(foo, 0),而是setTimeout(foo, delay),其中delay 至少有时大于0
猜你喜欢
  • 2018-07-09
  • 2016-02-15
  • 2017-07-23
  • 1970-01-01
  • 2016-03-13
  • 1970-01-01
  • 1970-01-01
  • 2022-10-07
  • 1970-01-01
相关资源
最近更新 更多