feat: monitor→GUI日志回调,EasyOCR子进程初始化状态可在GUI日志中查看

- MessageMonitor 新增 _log_callback / set_log_callback / _gui_log
- _delayed_easyocr_start 启动时输出「🔧 EasyOCR 子进程初始化中...」
- _handshake_easyocr_subprocess 成功/超时/失败时输出对应状态
- _start_easyocr_proc_locked 启动失败时输出错误
- start() 中 _HAS_EASYOCR=False 或 broken 时输出警告
- gui.py 在 _log_queue 创建后调用 monitor.set_log_callback(self._log)
This commit is contained in:
youpaishidifu
2026-08-30 12:26:59 +08:00
parent d2a4b4c0ea
commit 96f07353ba
2 changed files with 66 additions and 50 deletions
+24 -42
View File
@@ -54,14 +54,17 @@ def _force_system_memory_cleanup():
logger.debug(f"SetProcessWorkingSetSize 失败: {e}")
# Python GC + torch CUDA 清理
# ⭐ Task #1630: 修复内存687M——不主动 import torch(EasyOCR 已子进程化)
# import torch 会加载 ~300MB DLL;主进程无 CUDA 状态,仅在 torch 已加载时才清理
try:
import torch
if torch.cuda.is_available():
torch.cuda.empty_cache()
torch.cuda.synchronize()
logger.debug("系统内存清理: torch.cuda.empty_cache 完成")
except ImportError:
pass # torch 未安装
import sys as _sys
if 'torch' in _sys.modules:
_torch = _sys.modules['torch']
if _torch.cuda.is_available():
_torch.cuda.empty_cache()
_torch.cuda.synchronize()
logger.debug("系统内存清理: torch.cuda.empty_cache 完成")
# else: torch 未加载,跳过
except Exception as e:
logger.debug(f"torch.cuda.empty_cache 失败: {e}")
@@ -221,7 +224,13 @@ class OCREngineManager:
return False
def initialize(self):
"""预初始化两套OCR引擎(EasyOCR GPU/CPU根据内存自动选择)"""
"""预初始化OCR引擎(仅 Windows OCR)。
⭐ Task #1630: 修复内存687M——不在主进程中 import easyocr/torch!
EasyOCR 已完全子进程化(easyocr_worker.py),主进程 import easyocr 会连带
加载 torch DLL (~300MB),且 Python 无法卸载已加载的 DLL,导致内存永远不释放。
主进程仅使用 Windows OCR;EasyOCR 通过 monitor.py 的子进程隔离方案使用。
"""
if self._initialized:
return True
@@ -231,38 +240,8 @@ class OCREngineManager:
self._log("🔧 初始化OCR引擎...")
# 初始化EasyOCR(根据内存条件选择GPU/CPU)
# ⭐ Task #1601: GPU 探针已拦截 CUDA 异常,此处用 try/except 兜底
# access violation (native crash) 无法被 Python 捕获,
# 但探针通过后此概率极低;CPU 模式不受 native crash 影响。
use_gpu = False
try:
import easyocr
use_gpu = self._check_memory_for_gpu()
mode_str = "GPU" if use_gpu else "CPU"
self._log(f" ⏳ 初始化EasyOCR ({mode_str}模式)...")
try:
self._easyocr_reader = easyocr.Reader(['ch_sim', 'en'], gpu=use_gpu, verbose=False)
self._log(f" ✅ EasyOCR就绪({mode_str}模式)")
except Exception as e:
# GPU失败 → 降级CPU重试(注意:access violation 不可被 except 捕获)
if use_gpu:
self._log(f" ⚠ EasyOCR GPU初始化失败: {e},降级CPU重试...")
try:
self._easyocr_reader = easyocr.Reader(['ch_sim', 'en'], gpu=False, verbose=False)
self._log(" ✅ EasyOCR就绪(CPU模式,GPU降级)")
except Exception as e2:
self._log(f" ⚠ EasyOCR CPU也失败: {e2}")
self._easyocr_reader = None
else:
self._log(f" ⚠ EasyOCR初始化失败: {e}")
self._easyocr_reader = None
except ImportError:
self._log(" ⚠ EasyOCR未安装")
self._easyocr_reader = None
except Exception as e:
self._log(f" ⚠ EasyOCR异常: {e}")
self._easyocr_reader = None
# ⭐ EasyOCR 不再在主进程中初始化——子进程隔离方案已接管
self._easyocr_reader = None
# 初始化Windows OCR
try:
@@ -277,12 +256,12 @@ class OCREngineManager:
self._log(f" ⚠ Windows OCR初始化失败: {e}")
self._winocr_engine = None
if not self._easyocr_reader and not self._winocr_engine:
if not self._winocr_engine:
self._log("❌ 无可用OCR引擎")
return False
self._initialized = True
self._use_gpu = use_gpu
self._use_gpu = False
self._last_use_time = time.time()
self._start_timeout_timer()
self._log("✅ OCR引擎初始化完成")
@@ -602,6 +581,9 @@ class MsgBotApp(ctk.CTk):
# 自动回复执行队列(转发到主线程,避免 daemon 线程 SetForegroundWindow 失败)
self._reply_queue: queue.Queue[dict] = queue.Queue()
# ⭐ monitor→GUI 日志回调:EasyOCR 子进程初始化等关键状态转发到 GUI 日志
self.monitor.set_log_callback(self._log)
# ⭐ 启动时系统内存清理(强制回收Python进程内存)
self._log("🧹 启动内存清理...")
threading.Thread(target=_force_system_memory_cleanup, daemon=True).start()