背景
目前主进程使用 timer.setInterval 来做间隔任务执行,但是总有用户反馈养号卡主不执行了,或者某个操作不执行了,为了排除主进程的运行造成 setInterval 阻塞可能,将 setInterval 单独处理,可以排除主进程对定时器的影响。
此外,现在每秒定时会造成很多判断动作的浪费(每个人开发时都要做这种判断操作),这就需要一层封装,将自己需要的间隔操作注册到定时服务里面去,在符合条件时再收到该操作,让开发者专注处理自己的逻辑。
Worker 作为定时器的优点
-
隔离性:定时任务不会阻塞主线程,主线程可以专注于其他任务。
-
可靠性:Worker 线程的定时触发更精准,不受主线程负载影响。
-
可管理性:一个统一的 setInterval 管理所有事件,便于扩展和维护。
-
模块化:定时逻辑封装在 Worker 中,主进程代码更简洁。
-
大规模支持:适合管理大量定时任务,性能开销更可控。
-
错误隔离:Worker 的错误不会影响主进程,便于调试。
-
复杂逻辑支持:Worker 提供独立环境,适合实现复杂的定时功能。
-
线程安全:通过 postMessage 通信,确保事件触发安全。
几个知识点
-
worker.js 可以用 webpack 配置中 target="node"进行打包处理
-
npm run dev 时的路径需要用到 process.cwd() 提取,这是 electron 开发环境常规操作
实现步骤
-
主进程中引入一个心跳闹钟类,负责存储注册事件以及接收来自 worker 的间隔事件
-
worker.js 负责接收来自主进程的注册事件,通过一个 setInterval 和求余事件,向主进程这边发送对应事件,主进程的心跳闹钟类会根据注册事件列表,来调度不同的事件下的回调函数,执行具体逻辑。
源代码实现
// HeartbeatClick.js
const { Worker } = require('worker_threads');
const path = require('path');
const HEARTBEAT_LOG_PREFIX = '[Main - HeartbeatClock]';
class HeartbeatClock {
constructor() {
this.worker = null;
this.eventHandlers = new Map(); // Map<eventName, Array<Function>>
this.workerPath = path.join(process.cwd(), 'src', 'main', 'worker/heartbeat.worker.js');
}
// 启动 Worker
start() {
if (this.worker) {
console.log(`${HEARTBEAT_LOG_PREFIX} Worker already running.`);
return;
}
this.worker = new Worker(this.workerPath);
this.worker.on('message', this.handleWorkerMessage.bind(this));
this.worker.on('error', this.handleWorkerError.bind(this));
this.worker.on('exit', this.handleWorkerExit.bind(this));
console.log(`${HEARTBEAT_LOG_PREFIX} Worker started.`);
// 重新注册已有的事件
this.eventHandlers.forEach((_, eventName) => {
this.worker.postMessage({ type: 'register', eventName });
});
}
// 停止 Worker
stop() {
if (this.worker) {
this.worker.terminate();
this.worker = null;
console.log(`${HEARTBEAT_LOG_PREFIX} Worker stopped.`);
}
}
// 注册定时事件
registerEvent(eventName, handler) {
if (!eventName.startsWith('interval:') || !eventName.endsWith('s')) {
throw new Error('事件名格式错误,应为 interval:Ns,例如 interval:1s');
}
if (typeof handler !== 'function') {
throw new Error('handler 必须是一个函数');
}
// 如果事件名不存在,初始化一个空数组
if (!this.eventHandlers.has(eventName)) {
this.eventHandlers.set(eventName, []);
}
// 将回调函数添加到数组中
const handlers = this.eventHandlers.get(eventName);
handlers.push(handler);
console.log(`${HEARTBEAT_LOG_PREFIX} Registered handler for event: ${eventName}, total handlers: ${handlers.length}`);
// 如果 Worker 已启动,通知 Worker 注册事件
if (this.worker) {
this.worker.postMessage({ type: 'register', eventName });
console.log(`${HEARTBEAT_LOG_PREFIX} Notified worker of event: ${eventName}`);
}
}
// 注销定时事件
unregisterEvent(eventName, handler) {
if (!this.eventHandlers.has(eventName)) {
console.log(`${HEARTBEAT_LOG_PREFIX} Event not found: ${eventName}`);
return;
}
const handlers = this.eventHandlers.get(eventName);
// 如果提供了特定的 handler,则只移除该 handler
if (handler && typeof handler === 'function') {
const index = handlers.indexOf(handler);
if (index !== -1) {
handlers.splice(index, 1);
console.log(`${HEARTBEAT_LOG_PREFIX} Unregistered specific handler for event: ${eventName}, remaining handlers: ${handlers.length}`);
}
} else {
// 如果未提供 handler,则移除所有 handler
handlers.length = 0;
console.log(`${HEARTBEAT_LOG_PREFIX} Unregistered all handlers for event: ${eventName}`);
}
// 如果没有 handler 了,删除事件并通知 Worker
if (handlers.length === 0) {
this.eventHandlers.delete(eventName);
if (this.worker) {
this.worker.postMessage({ type: 'unregister', eventName });
console.log(`${HEARTBEAT_LOG_PREFIX} Notified worker to unregister event: ${eventName}`);
}
}
}
// 处理 Worker 发送的消息
handleWorkerMessage(message) {
if (message.type === 'interval') {
const eventName = message.eventName;
if (this.eventHandlers.has(eventName)) {
const handlers = this.eventHandlers.get(eventName);
handlers.forEach((handler, index) => {
try {
handler();
} catch (err) {
console.error(`${HEARTBEAT_LOG_PREFIX} Error in handler ${index} for event ${eventName}:`, err);
}
});
} else {
console.log(`${HEARTBEAT_LOG_PREFIX} Unregistered event: ${eventName}`);
}
}
}
// 处理 Worker 错误
handleWorkerError(err) {
console.error(`${HEARTBEAT_LOG_PREFIX} Worker error:`, err);
}
// 处理 Worker 退出
handleWorkerExit(code) {
console.log(`${HEARTBEAT_LOG_PREFIX} Worker exited with code: ${code}`);
this.worker = null;
}
}
export default HeartbeatClock;
// main.js
import HeartbeatClock from './HeartbeatClock';
app.on('ready', () => {
createMainWindow();
const heartbeatClock = new HeartbeatClock();
heartbeatClock.start();
// 示例:注册定时事件
heartbeatClock.registerEvent('interval:1s', () => {
console.log('每 1 秒执行的任务 ----- 1', Date.now());
});
heartbeatClock.registerEvent('interval:1s', () => {
console.log('每 1 秒执行的任务 ----- 2', Date.now());
});
heartbeatClock.registerEvent('interval:1s', () => {
console.log('每 1 秒执行的任务 ----- 3', Date.now());
});
heartbeatClock.registerEvent('interval:3s', () => {
console.log('每 3 秒执行的任务', Date.now());
});
heartbeatClock.registerEvent('interval:5s', () => {
console.log('每 5 秒执行的任务', Date.now());
});
heartbeatClock.registerEvent('interval:10s', () => {
console.log('每 10 秒执行的任务', Date.now());
});
});
// heartbeat.worker.js
const { parentPort } = require('worker_threads');
// 存储注册的间隔(秒数)和对应的事件名
const intervals = new Map(); // { intervalSeconds: eventName }
let tickCount = 0; // 计数器,从 0 开始,每秒递增
const tickInterval = 1000; // 1 秒
// 每秒递增计数器并检查事件
const ticker = setInterval(() => {
tickCount++; // 计数器递增
intervals.forEach((eventName, intervalSeconds) => {
if (tickCount % intervalSeconds === 0) {
console.log(`[Worker] 触发事件: ${eventName}, tickCount: ${tickCount}`);
parentPort.postMessage({ type: 'interval', eventName });
}
});
// 可选:防止 tickCount 过大(虽然 JavaScript 数字类型安全,但仍可重置)
if (tickCount >= Number.MAX_SAFE_INTEGER) {
tickCount = 0;
console.log('[Worker] 重置计数器');
}
}, tickInterval);
// 监听主进程的消息
parentPort.on('message', (message) => {
if (message.type === 'register') {
const eventName = message.eventName; // 例如 "interval:30s"
const intervalSeconds = parseInt(eventName.split(':')[1]); // 解析出秒数,例如 30
if (!intervals.has(intervalSeconds)) {
intervals.set(intervalSeconds, eventName);
console.log(`[Worker] 已注册事件: ${eventName}, interval: ${intervalSeconds}s`);
}
} else if (message.type === 'unregister') {
const eventName = message.eventName;
const intervalSeconds = parseInt(eventName.split(':')[1]);
if (intervals.has(intervalSeconds)) {
intervals.delete(intervalSeconds);
console.log(`[Worker] 已注销事件: ${eventName}`);
}
}
});
// 清理逻辑
parentPort.on('close', () => {
clearInterval(ticker);
intervals.clear();
tickCount = 0;
console.log('[Worker] 已清理定时器和事件');
});
// 处理 Worker 错误
parentPort.on('error', (err) => {
console.error('[Worker] 错误:', err);
});