简体   繁体   English

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

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

The Problem:问题:

I have encountered a scenario using Javascript in NW.js whereupon setTimeout() will experience arbitrarily long delays instead of executing the callback after the requested duration.我遇到过在 NW.js 中使用 Javascript 的场景,因此setTimeout()会遇到任意长的延迟,而不是在请求的持续时间之后执行回调。 Generally anywhere from a few dozen milliseconds to thousands of milliseconds of unexpected delay.一般在任何地方都有几十毫秒到几千毫秒的意外延迟。

Of course, setTimeout() doesn't guarantee it will execute the callback precisely when requested, but it's usually very, very close.My expectation would be that this would always be the case, so long as the main thread wasn't overloaded with other tasks.当然, setTimeout()并不能保证它会在请求时准确地执行回调,但它通常非常非常接近。我的期望是情况总是如此,只要主线程没有过载其他任务。 For instance, if I mash F12 and bring up my browser console and run the below:例如,如果我混搭 F12 并打开浏览器控制台并运行以下命令:

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

I would expect output like:我希望输出如下:

This should run immediately! It took 1ms This should run immediately! It took 1ms (possibly 0-5ms, depending - but definitely very little time, and repeatably so if you run it again and again) This should run immediately! It took 1ms (可能是 0-5 毫秒,视情况而定——但绝对是很少的时间,而且如果你一次又一次地运行它,它是可重复的)

However, in the aforementioned scenario, the delay can be significant;但是,在上述情况下,延迟可能很大; often thousands of milliseconds longer than the duration requested.通常比请求的持续时间长数千毫秒。

The Scenario:场景:

All it seemingly requires is for setTimeout() to be used after an onData event from a child process.它似乎只需要在来自子进程的 onData 事件之后使用setTimeout() It can be used directly in the process.stdout.on('data') callback, or it can be used some time later in the chain of execution that might follow.它可以直接在process.stdout.on('data')回调中使用,也可以在稍后的执行链中使用。 In both cases, setTimeout() seems to misbehave.在这两种情况下, setTimeout()似乎行为不端。

Nothing else misbehaves;没有其他行为不端; Javascript continues to execute as normal, Promises resolve in a timely fashion, etc etc... just so long as setTimeout() isn't involved. Javascript 继续正常执行,Promises 及时解决,等等......只要不涉及setTimeout() Even horrible things like using For loops to impose a delay instead of setTimeout() will work reliably.即使像使用 For 循环来强加延迟而不是setTimeout()这样可怕的事情也能可靠地工作。

I have prepared an example which replicates what I am observing on my systems, in the hopes others could test it and see if perhaps I am doing something wrong!我准备了一个例子来复制我在我的系统上观察到的东西,希望其他人可以测试它,看看我是否做错了什么!

I am hoping someone will turn around and tell me I've overlooked something!我希望有人会转过身来告诉我我忽略了一些东西!

Please see below for the code.请参阅下面的代码。 I've tried it on multiple machines with the same behaviour observed.我已经在多台机器上尝试过,观察到相同的行为。 Please note the test case given is the bare minimum example I've thus far discovered which replicates the issue.请注意给出的测试用例是我迄今为止发现的最低限度的例子,它复制了这个问题。 It is not representative of what I am actually trying to do in practice ;它并不代表我在实践中实际尝试做的事情 instead, it just attempts to replicate the misbehaving setTimeout() issue I have encountered on my project (and it does replicate it, as far as I can tell).相反,它只是试图复制我在我的项目中遇到的行为不端的setTimeout()问题(据我所知,它确实复制了它)。

Working Example:工作示例:

This scenario requires:这个场景需要:

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

Download and unzip the relevant NW.js SDK, then plonk the following files in the folder and run nw.exe .下载并解压相关的 NW.js SDK,然后在文件夹中插入以下文件并运行nw.exe You should see a white window appear;您应该会看到一个白色窗口出现; open devtools (F12) and perform a test with td.beginBasicTest() :打开 devtools (F12) 并使用td.beginBasicTest()执行测试:

package.json:包.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: 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()

test.html:测试.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>

When I run the tests a few times, I frequently observe setTimeout seeming to misbehave.当我多次运行测试时,我经常观察到 setTimeout 似乎行为不端。 Here is some example output I got while running the test a few times:这是我在多次运行测试时得到的一些示例输出:

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

the fact that sometimes your immediate/5 second reports at 1ms/5010ms and sometimes reports as 8714ms/8715ms tells mes that this is an issue where your event loop is full and waiting on something else to complete before the setTimeout can run.有时您的立即/5 秒报告为1ms/5010ms ,有时报告为8714ms/8715ms告诉我,这是一个问题,您的事件循环已满并在setTimeout可以运行之前等待其他事情完成。

I don't know enough about stdio/stdout/stderr to offer much more than that.我对 stdio/stdout/stderr 的了解还不够多。

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

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