我目前正在尝试学习nodejs,我正在做的一个小项目正在编写一个API来控制一些联网的LED灯。
控制 LED 的微处理器有处理延迟,我需要将发送到微处理器的命令间隔至少 100 毫秒。在C#中我习惯只调用Thread.Sleep(time),但我在node中没有找到类似的功能。
我在节点中使用 setTimeout(...) 函数找到了几种解决方案,但是,这是异步的并且不会阻塞线程(这是我在这种情况下需要的)。
有人知道阻塞睡眠或延迟功能吗?最好是不只是旋转 CPU,并且精度为 +-10 毫秒的东西?
Node 本质上是异步的,这就是它的伟大之处,所以你真的不应该阻塞线程,但由于这似乎是一个控制 LED 的项目,无论如何我都会发布一个解决方法,即使它不是一个非常好的一个,不应该使用(说真的)。
while 循环会阻塞线程,因此您可以创建自己的睡眠函数
function sleep(time, callback) {
var stop = new Date().getTime();
while(new Date().getTime() < stop + time) {
;
}
callback();
}
用作
sleep(1000, function() {
// executes after one second, and blocks the thread
});
我认为这是阻塞线程(原则上)的唯一方法,使其在循环中保持忙碌,因为 Node 没有内置任何阻塞功能,因为它会破坏异步行为的目的。
使用 ECMA 脚本 2017(Node 7.6 及以上版本支持),它就变成了一行:
function sleep(millis) {
return new Promise(resolve => setTimeout(resolve, millis));
}
// Usage in async function
async function test() {
await sleep(1000)
console.log("one second has elapsed")
}
// Usage in normal function
function test2() {
sleep(1000).then(() => {
console.log("one second has elapsed")
});
}
最好的解决方案是为您的 LED 创建单例控制器,它将对所有命令进行排队并以指定的延迟执行它们:
function LedController(timeout) {
this.timeout = timeout || 100;
this.queue = [];
this.ready = true;
}
LedController.prototype.send = function(cmd, callback) {
sendCmdToLed(cmd);
if (callback) callback();
// or simply `sendCmdToLed(cmd, callback)` if sendCmdToLed is async
};
LedController.prototype.exec = function() {
this.queue.push(arguments);
this.process();
};
LedController.prototype.process = function() {
if (this.queue.length === 0) return;
if (!this.ready) return;
var self = this;
this.ready = false;
this.send.apply(this, this.queue.shift());
setTimeout(function () {
self.ready = true;
self.process();
}, this.timeout);
};
var Led = new LedController();
现在您可以致电
Led.exec
,它会为您处理所有延误:
Led.exec(cmd, function() {
console.log('Command sent');
});
只需使用
child_process.execSync
并调用系统的睡眠功能即可。
//import child_process module
const child_process = require("child_process");
// Sleep for 5 seconds
child_process.execSync("sleep 5");
// Sleep for 250 microseconds
child_process.execSync("usleep 250");
// Sleep for a variable number of microseconds
var numMicroSeconds = 250;
child_process.execFileSync("usleep", [numMicroSeconds]);
我在主应用程序脚本顶部的循环中使用它,使 Node 等待网络驱动器连接后再运行应用程序的其余部分。
使用 Node sleep 包。 https://www.npmjs.com/package/sleep。
在您的代码中您可以使用
var sleep = require('sleep');
sleep.sleep(n)
睡眠特定的 n 秒。
最简单的真正的同步解决方案(即没有产量/异步)我可以想出在所有操作系统中都有效且没有任何依赖性的方法是调用节点进程来评估内联
setTimeout
表达式:
const sleep = (ms) => require("child_process")
.execSync(`"${process.argv[0]}" -e setTimeout(function(){},${ms})`);
正如睡眠包作者建议的那样*:
function msleep(n) {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n);
}
function sleep(n) {
msleep(n * 1000);
}
使用本机插件实现非常简单,所以有人这样做了:https://github.com/ErikDubbelboer/node-sleep.git
function AnticipatedSyncFunction(){
var ret;
setTimeout(function(){
var startdate = new Date()
ret = "hello" + startdate;
},3000);
while(ret === undefined) {
require('deasync').runLoopOnce();
}
return ret;
}
var output = AnticipatedSyncFunction();
var startdate = new Date()
console.log(startdate)
console.log("output="+output);`
独特的问题是打印的日期不正确,但该过程至少是连续的。
即使在开发严格的硬件解决方案时,Node.js 中的阻塞也是不必要的。 请参阅 temporal.js,它不使用
setTimeout
或 setInterval
setImmediate。 相反,它使用 setImmediate
或 nextTick
提供更高分辨率的任务执行,并且您可以创建任务的线性列表。 但你可以在不阻塞线程的情况下做到这一点。
您可以简单地使用 ECMA6 和
yield
库中引入的 gen-run
功能:
let run = require('gen-run');
function sleep(time) {
return function (callback) {
setTimeout(function(){
console.log(time);
callback();
}, time);
}
}
run(function*(){
console.log("befor sleeping!");
yield sleep(2000);
console.log("after sleeping!");
});
异步调用
ping
命令阻止当前代码在指定毫秒内执行。
ping
命令是跨平台的start /b
意思是:启动程序但是
不显示窗口。代码如下:
const { execSync } = require('child_process')
// delay(blocking) specified milliseconds
function sleep(ms) {
// special Reserved IPv4 Address(RFC 5736): 192.0.0.0
// refer: https://en.wikipedia.org/wiki/Reserved_IP_addresses
execSync(`start /b ping 192.0.0.0 -n 1 -w ${ms} > nul`)
}
// usage
console.log("delay 2500ms start\t:" + (new Date().getTime() / 1000).toFixed(3))
sleep(2500)
console.log("delay 2500ms end\t:" + (new Date().getTime() / 1000).toFixed(3))
重要注意:以上不是精确的解决方案,它只是接近阻塞时间
使用较新的 API,这是可能的:
function sleep(ms) {
const atomicsBuffer = new Int32Array(new SharedArrayBuffer(4));
Atomics.wait(atomicsBuffer, 0, 0, ms);
}
将等待,直到值不为 0,或毫秒超时。由于值永远不会改变,因此它本质上是阻塞的。 来源
阻塞主线程对于节点来说不是一种好的方式,因为在大多数情况下有不止一个人在使用它。您应该将 settimeout/setinterval 与回调结合使用。