簡體   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