繁体   English   中英

在来自子进程的数据事件中使用 setTimeout 是否有原因导致任意长的延迟?

[英]Is there a reason using setTimeout within an on-data event from a child process causes arbitrarily long delays?

问题:

我遇到过在 NW.js 中使用 Javascript 的场景,因此setTimeout()会遇到任意长的延迟,而不是在请求的持续时间之后执行回调。 一般在任何地方都有几十毫秒到几千毫秒的意外延迟。

当然, setTimeout()并不能保证它会在请求时准确地执行回调,但它通常非常非常接近。我的期望是情况总是如此,只要主线程没有过载其他任务。 例如,如果我混搭 F12 并打开浏览器控制台并运行以下命令:

var ts = Date.now();
setTimeout(function () {
  console.log(`This should run immediately! It took ${Date.now() - ts}ms`);
}, 0);

我希望输出如下:

This should run immediately! It took 1ms This should run immediately! It took 1ms (可能是 0-5 毫秒,视情况而定——但绝对是很少的时间,而且如果你一次又一次地运行它,它是可重复的)

但是,在上述情况下,延迟可能很大; 通常比请求的持续时间长数千毫秒。

场景:

它似乎只需要在来自子进程的 onData 事件之后使用setTimeout() 它可以直接在process.stdout.on('data')回调中使用,也可以在稍后的执行链中使用。 在这两种情况下, setTimeout()似乎行为不端。

没有其他行为不端; Javascript 继续正常执行,Promises 及时解决,等等......只要不涉及setTimeout() 即使像使用 For 循环来强加延迟而不是setTimeout()这样可怕的事情也能可靠地工作。

我准备了一个例子来复制我在我的系统上观察到的东西,希望其他人可以测试它,看看我是否做错了什么!

我希望有人会转过身来告诉我我忽略了一些东西!

请参阅下面的代码。 我已经在多台机器上尝试过,观察到相同的行为。 请注意给出的测试用例是我迄今为止发现的最低限度的例子,它复制了这个问题。 它并不代表我在实践中实际尝试做的事情 相反,它只是试图复制我在我的项目中遇到的行为不端的setTimeout()问题(据我所知,它确实复制了它)。

工作示例:

这个场景需要:

  • NW.js:最新的 0.49.2 SDK,也是 0.48.3 SDK 测试(相同的行为)
  • Python:最新的 3.9.0,也经过 3.8.5 测试(相同的行为)
  • Windows:测试了 v2004 版本 (19041.508) 和 (19041.572)(相同的行为)

下载并解压相关的 NW.js SDK,然后在文件夹中插入以下文件并运行nw.exe 您应该会看到一个白色窗口出现; 打开 devtools (F12) 并使用td.beginBasicTest()执行测试:

包.json:

{
  "name": "STT",
  "description": "setTimeout Test",
  "main": "test.html",
  "node-remote": "http://127.0.0.1",
  "window": {
    "title": "test",    
    "show": true,
    "toolbar": true,
    "fullscreen": false,
    "width": 500,
    "height": 500,
    "frame": false,
    "position": "center",
    "resizeable": true
  },
  "dependencies": {    
  },
  "devDependencies": {},
  "chromium-args": "--password-store=basic --disable-pinch --disable-background-timer-throttling --disable-raf-throttling"
}

child_process.py:

#!/usr/bin/env python3
import sys
import asyncio
import json

class TestProcess:
    def __init__(self):
        self.alive = True
        self.run = True        
            
    def __hello(self):
        hello_msg = '{"resp": "hello"}'
        print(hello_msg, flush=True)
            
    async def __stdin_msg_handler(self, inp):
        # read the input command and respond accordingly
        if(len(inp) > 0):
            try:
                # convert from JSON to dict
                msg = json.loads(inp[0])
            except:
                # couldn't decode message; ignore it?
                print('{"resp":"badCommand"}', flush=True)
                msg = None
            if(msg):
                if(msg["cmd"] == "hello"):
                    self.__hello()

    async def __listen(self):
        line = await self.loop.run_in_executor(None, sys.stdin.readline)
        if(len(line) > 0):
            msgs = line.split("\r")
            return msgs
        else:            
            return False

    async def __loop(self):
        while self.run == True:
            msgs = await self.__listen()
            if(msgs):
                await self.__stdin_msg_handler(msgs)

    def start(self):
        self.loop = asyncio.get_event_loop()
        self.loop.run_until_complete(self.__loop())


if __name__ == '__main__':
    tproc = TestProcess()
    tproc.start()

测试.html:

<head>

<script>
const spawn = require(`child_process`).spawn; // ability to execute background tasks/commands

class TestDelay {
  constructor (delay = 5) {
    this.childProcessPath = `child_process.py`;
    this._startProcess();
  }

  beginBasicTest (delay = this.defaultDelay) {
    this._pTestProcedureWithComms(delay)
      .then(() => {
        console.log(`Test completed - did it work? Try it a few times, performance seems to vary...`);
      });
  }

  _startProcess () {
    this.childProcess = spawn(`py`, [`-3`, this.childProcessPath], { stdio: `pipe` });
    this.childRunning = true;

    this.childProcess.stdout.on(`data`, (d) => {
      console.log(`stdout: ${d}`);
      var ts = Date.now();
      setTimeout(function () {
        console.log(`This should run immediately! It took ${Date.now() - ts}ms`);
      }, 0);
      setTimeout(function () {
        console.log(`This should run after 5s! It took ${Date.now() - ts}ms`);
      }, 5000);
    });

    this.childProcess.stderr.on(`data`, (d) => {
      console.warn(`stderr: ${d}`);
    });

    this.childProcess.on(`exit`, (code) => {
      this.childRunning = false;
      console.log(`child process exited with code ${code}`);
    });

    this.childProcess.on(`close`, (code) => {
      console.log(`child process IO closed with code ${code}`);
    });
    console.log(`Started child process...`);
  }

  _pTestProcedureWithComms (delay = this.defaultDelay) {
    return new Promise(resolve => {
      this._pSendToProcess({ cmd: `hello` })
        .then(resolve);
    });
  }

  _pSendToProcess (text = ``) {
    return new Promise(resolve => {
      this.childProcess.stdin.write(JSON.stringify(text), `utf-8`);
      this.childProcess.stdin.write(`\n`, `utf-8`);
      resolve();
    });
  }
}

var td = new TestDelay();
</script>
</head>
<body>
Hello!
</body>

当我多次运行测试时,我经常观察到 setTimeout 似乎行为不端。 这是我在多次运行测试时得到的一些示例输出:

行为异常的 setTimeout 控制台屏幕截图

有时您的立即/5 秒报告为1ms/5010ms ,有时报告为8714ms/8715ms告诉我,这是一个问题,您的事件循环已满并在setTimeout可以运行之前等待其他事情完成。

我对 stdio/stdout/stderr 的了解还不够多。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM