时间队列模块报错
时间队列模块报错
#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 调用绑定 与窗口实例绑定 获取方式 被动接收 主动查找 可靠性 可能过期 实时查找,更可靠