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');
    }

    // 启动 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);
});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

森叶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值