当前位置: 首页 > news >正文

Electron 主进程中使用Worker来创建不同间隔的定时器实现过程

背景

目前主进程使用 timer.setInterval 来做间隔任务执行,但是总有用户反馈养号卡主不执行了,或者某个操作不执行了,为了排除主进程的运行造成 setInterval 阻塞可能,将 setInterval 单独处理,可以排除主进程对定时器的影响。

此外,现在每秒定时会造成很多判断动作的浪费(每个人开发时都要做这种判断操作),这就需要一层封装,将自己需要的间隔操作注册到定时服务里面去,在符合条件时再收到该操作,让开发者专注处理自己的逻辑。

Worker 作为定时器的优点

  1. 隔离性:定时任务不会阻塞主线程,主线程可以专注于其他任务。

  2. 可靠性:Worker 线程的定时触发更精准,不受主线程负载影响。

  3. 可管理性:一个统一的 setInterval 管理所有事件,便于扩展和维护。

  4. 模块化:定时逻辑封装在 Worker 中,主进程代码更简洁。

  5. 大规模支持:适合管理大量定时任务,性能开销更可控。

  6. 错误隔离:Worker 的错误不会影响主进程,便于调试。

  7. 复杂逻辑支持:Worker 提供独立环境,适合实现复杂的定时功能。

  8. 线程安全:通过 postMessage 通信,确保事件触发安全。

几个知识点

  1. worker.js 可以用 webpack 配置中 target="node"进行打包处理

  2. npm run dev 时的路径需要用到 process.cwd() 提取,这是 electron 开发环境常规操作

实现步骤

  1. 主进程中引入一个心跳闹钟类,负责存储注册事件以及接收来自 worker 的间隔事件

  2. 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');}// 启动 Workerstart() {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 });});}// 停止 Workerstop() {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,则只移除该 handlerif (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,则移除所有 handlerhandlers.length = 0;console.log(`${HEARTBEAT_LOG_PREFIX} Unregistered all handlers for event: ${eventName}`);}// 如果没有 handler 了,删除事件并通知 Workerif (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]); // 解析出秒数,例如 30if (!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);
});

http://www.dtcms.com/a/192305.html

相关文章:

  • Electron 应用的升级机制详解
  • Electron详解:原理与不足
  • Windows 环境下 Docker Desktop 安装 + 汉化
  • MinerU安装(pdf转markdown、json)
  • win11平台下的docker-desktop中的volume位置问题
  • UR5e机器人Matlab仿真
  • 前后端设置跨域并从后端允许发送cookie
  • 【ROS2】ROS节点启动崩溃:rclcpp::exceptions::RCLInvalidArgument
  • 【Python】杂乱-[代码]python 批量修改指定文件/目录的名称
  • linux系统中如何校准时间
  • 卡洛诗,将高端西餐的冗余价值转化为普惠体验
  • 【JS】vue3中组件命名问题
  • 不建议在useEffect中进行数据获取的理由
  • 火山引擎发展方向
  • 【Hadoop】伪分布式安装
  • AbstractErrorController简介-笔记
  • Qt串口通信粘包拆包解决方案
  • 【虚幻引擎】UE5独立游戏开发全流程(商业级架构)
  • 说一说Node.js高性能开发中的I/O操作
  • 线代第二章矩阵第九、十节:初等变换、矩阵的标准形、阶梯形与行最简阶梯形、初等矩阵
  • 2025长三角杯数学建模A题思路模型代码:智能手机产品设计优化与定价问题
  • uniapp+vite+cli模板引入tailwindcss
  • Java - Junit框架
  • kafka调优
  • CSS相关知识补充
  • 代码分支操作步骤
  • 关于在深度聚类中Representation Collapse现象
  • Kafka消费者分组机制深度解析
  • C语言_自动义类型:联合和枚举
  • [ctfshow web入门] web75