diff --git a/src/gui/core/pipeline_executor.py b/src/gui/core/pipeline_executor.py index 16895c2..2b393b1 100644 --- a/src/gui/core/pipeline_executor.py +++ b/src/gui/core/pipeline_executor.py @@ -492,7 +492,14 @@ class PipelineExecutor(QObject): # ═══════════════════════════════════════════════════════════ def _on_log_message(self, message: str, level: str): - """WorkerThread 日志 → EventBus LogMessage 事件。""" + """WorkerThread 日志 → EventBus LogMessage 事件。 + + 同时重置看门狗计时器:任何来自 Worker 的日志消息都代表 Worker 仍然存活。 + 这解决了长时间运行的 C/NumPy 计算(如 Goodman 逐波段处理)期间 + 因无进度更新而被误杀的问题。 + """ + import time + self._last_progress_time = time.time() global_event_bus.publish('LogMessage', { 'message': message, 'level': level, @@ -541,14 +548,14 @@ class PipelineExecutor(QObject): 判定逻辑: - Worker 已不在运行(自然结束/被强杀) → 停掉看门狗 - - Worker 仍在运行 + 上次进度距今 > 600 秒 → 判定为假死/死锁, + - Worker 仍在运行 + 上次通讯距今 > 3600 秒 → 判定为假死/死锁, 先尝试 WorkerThread.stop() 优雅停止(5 秒超时), 若优雅停止也超时则 fallback 到 terminate() """ import time if self._worker and self._worker.isRunning(): - if time.time() - self._last_progress_time > 600: - self._log_message("[错误] 后台任务响应超时(超过10分钟无响应),尝试优雅停止...", "error") + if time.time() - self._last_progress_time > 3600: + self._log_message("[错误] 后台任务响应超时(超过1小时无任何通讯),尝试优雅停止...", "error") # 先尝试通过 cancel + wait 优雅停止 if hasattr(self._worker, 'stop'): try: diff --git a/src/gui/core/worker_thread.py b/src/gui/core/worker_thread.py index 182d9df..9d3e49a 100644 --- a/src/gui/core/worker_thread.py +++ b/src/gui/core/worker_thread.py @@ -218,6 +218,9 @@ class WorkerThread(QThread): k for k in self.config.keys() if k not in self.skip_list ]) if self.mode == 'full' else 1 + # ★ 心跳线程:防止长时间 C/NumPy 运算期间看门狗超时 + self._heartbeat_stop = None # threading.Event + def pipeline_callback(self, step_name, status, message=""): """Pipeline回调函数,用于接收步骤状态""" if status == "start": @@ -277,6 +280,23 @@ class WorkerThread(QThread): pass mpl_prev = None + + # ★ 启动心跳线程:每 30s 向主线程发送一次保活信号 + # 防止 Goodman/Kutser 等逐波段 C 运算期间因无 Qt 信号而被看门狗误杀 + import threading as _threading + self._heartbeat_stop = _threading.Event() + _heartbeat_interval = 30.0 + + def _heartbeat_loop(): + while not self._heartbeat_stop.is_set(): + self._heartbeat_stop.wait(_heartbeat_interval) + if not self._heartbeat_stop.is_set(): + # 心跳不打印可见日志(避免刷屏),但必须发射信号以重置看门狗 + self.log_message.emit("[心跳] 后台处理中...", "info") + + _heartbeat_thread = _threading.Thread(target=_heartbeat_loop, daemon=True) + _heartbeat_thread.start() + try: # ★ 终端即时反馈 print(f"\n[WorkerThread] 后台线程启动 (mode={self.mode}, work_dir={self.work_dir})") @@ -354,6 +374,10 @@ class WorkerThread(QThread): self.finished.emit(False, f"后台线程崩溃: {e}\n\n{full_tb}") finally: + # ── 停止心跳线程 ── + if self._heartbeat_stop is not None: + self._heartbeat_stop.set() + # ── 恢复 Matplotlib 后端 ── if mpl_prev: try: