时间队列模块报错

时间队列模块报错

#tech / dev / desktop #type / howto #status / growing #resource / electron

[!info] related notes


起因

使用 提醒功能 时,没有等到弹窗提醒,却等到了弹窗报错

A JavaScript error occurred in the main process Uncaught Exception:
TypeError: Object has been destroyed
at _send (node:electron/js2c/browser_init:2:73623) at ar.job
(file:///C:/Program%20Files/DailyUse/resources/app/dist-electron/main.js:14462:18) at ar.invoke
(file://C:/Program%20Files/DailyUse/resources/app/dist-electron/main.js:14357:64) at
file:///C:/Program%20Files/DailyUse/resources/app/dist-electron/main.js:14259:21 at Timeout._onTimeout
(file:///C:/Program%20Files/DailyUse/resources/app/dist-electron/main,js:14240:33) at listOnTimeout (node:internal/timers:581:17)
at process.processTimers (node:internal/timers:519:7)

错误分析

错误代码

ipcMain.handle(
  "create-schedule",
  async (
    _event,
    options: {
      id: string;
      cron: string;
      task: {
        type: string;
        payload: any;
      };
      lastRun: string;
    }
  ) => {
    try {
      // 如果已存在相同ID的任务,先删除
      if (scheduleJobs.has(options.id)) {
        scheduleJobs.get(options.id)?.cancel();
      }

      // 创建新的定时任务
      const job = nodeSchedule.scheduleJob(options.cron, () => {
        // 任务执行时通知渲染进程
        // 这里的 event 对象来自外部闭包
        // 当定时任务执行时,原始的 event 对象可能已经被销毁
        _event.sender.send("schedule-triggered", {  
          id: options.id,
          task: options.task,
        });
      });

      scheduleJobs.set(options.id, job);
      return true;
    } catch (error) {
      console.error("Failed to create schedule:", error);
      return false;
    }
  }
);

猜测应该是:
提醒任务时间到了,调用 _event.sender.send() ,但对象已经被销毁,结果报错了

修改

不使用 event 对象来发送,而是使用 win.webContents.send 来发送


// 找到启用的窗口
function getValidWindow(): BrowserWindow | null {
    const windows = BrowserWindow.getAllWindows();
    return windows.find(win => !win.isDestroyed()) || null;
}

ipcMain.handle('create-schedule', async (_event, options: {
        id: string
        cron: string
        task: {
            type: string
            payload: any
          }
        lastRun: string
    }) => {
        try {
            // 如果已存在相同ID的任务,先删除
            if (scheduleJobs.has(options.id)) {
                scheduleJobs.get(options.id)?.cancel();
            }

            // 创建新的定时任务
            const job = nodeSchedule.scheduleJob(options.cron, () => {
                // 任务执行时通知渲染进程
                const win = getValidWindow();
                if (win) {
                    try {
                        win.webContents.send('schedule-triggered', {
                            id: options.id,
                            task: options.task
                        });
                    } catch (error) {
                        console.error('Failed to send schedule-triggered event:', error);
                    }
                }
            });

            scheduleJobs.set(options.id, job);
            return true;
        } catch (error) {
            console.error('Failed to create schedule:', error);
            return false;
        }
    });

小问题

闭包引用的对象不是不会被销毁吗,还有个闭包内存泄漏的问题不就来源于此吗?这里怎么会被销毁了

闭包确实会保持对象引用不被垃圾回收,但这里的情况比较特殊:
闭包保持引用 ≠ 对象仍然有效


// 闭包会保持这些引用
const job = nodeSchedule.scheduleJob(options.cron, () => {
    // options.id - 字符串,没问题
    // options.task - 普通对象,没问题
    // 但是如果这里有 event.sender,情况就不同了
});

在 Electron 中,event.sender (WebContents) 有自己的生命周期管理:


// 问题示例:
ipcMain.handle('create-schedule', async (event, options) => {
    const job = nodeSchedule.scheduleJob(options.cron, () => {
        // 虽然闭包保持了 event 的引用
        // 但 event.sender 指向的 WebContents 可能已经被 Electron 内部销毁
        if (event.sender.isDestroyed()) {
            console.log('WebContents 已被销毁,但闭包仍持有引用!');
        }
        
        try {
            event.sender.send('message'); // 这里会抛出 "Object has been destroyed"
        } catch (error) {
            console.error('对象已销毁:', error);
        }
    });
});

event.sender 和 win.webContents 的关系

功能上相同,但来源和生命周期不同:


// 都是 WebContents 对象,有相同的方法
event.sender.send('message', data);
win.webContents.send('message', data);

// 都可以检查是否被销毁
event.sender.isDestroyed();
win.webContents.isDestroyed();

特性 event.sender win.webContents 来源 IPC 事件中获取 BrowserWindow 实例获取 生命周期 与 IPC 调用绑定 与窗口实例绑定 获取方式 被动接收 主动查找 可靠性 可能过期 实时查找,更可靠

创建于 2025/1/1 更新于 2026/5/27