修复OCR截图BitBlt卡死DWM→整机冻结2-3分钟
根因: ImageGrab.grab() 底层用 GDI BitBlt 从 DWM 合成表面截屏。 _ensure_visible(SwitchToThisWindow) 触发 DWM 窗口激活合成后, 紧接着 BitBlt 在 DWM 繁忙时阻塞等待→DWM 死锁→桌面冻结 2-3 分钟 →DWM 重启→部分使用 GPU 的软件崩溃 修复(新增 _safe_screenshot): - daemon 线程内 ImageGrab.grab(), join(timeout=3.0) 超时等待 - 超时后线程 is_alive→返回 None, 跳过本轮(日志: BitBlt 可能卡在 DWM) - 不会 cancel 线程(无法取消阻塞的 C 调用), 但 daemon 特性确保: 进程退出时自动终止, 正常运行时 GDI 调用最终完成+线程自动回收 - _ensure_visible 后 sleep 从 0.1s 增加到 0.4s, 给 DWM 完成窗口合成 降低 BitBlt 在 DWM 繁忙时调用的概率
This commit is contained in:
+33
-4
@@ -1049,6 +1049,29 @@ class MessageMonitor:
|
||||
)
|
||||
return (win_text, chosen) if chosen == "windows" else (easy_text, chosen)
|
||||
|
||||
@staticmethod
|
||||
def _safe_screenshot(bbox: tuple, timeout: float = 3.0):
|
||||
"""带超时的屏幕截图。ImageGrab.grab() 的 BitBlt 可能因 DWM 卡死→整机冻结 2-3min。
|
||||
|
||||
用线程+timeout 隔离:BitBlt 卡死只杀线程,不波及 DWM/桌面。
|
||||
timeout=3.0: 3 秒截不到就跳过本轮,bot 继续下次轮询。
|
||||
"""
|
||||
_im = [None]
|
||||
def _grab():
|
||||
try:
|
||||
_im[0] = ImageGrab.grab(bbox=bbox)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
import threading
|
||||
t = threading.Thread(target=_grab, daemon=True)
|
||||
t.start()
|
||||
t.join(timeout=timeout)
|
||||
if t.is_alive():
|
||||
logger.warning(f"[ocr] 截图超时 ({timeout}s),BitBlt 可能卡在 DWM,跳过本轮")
|
||||
return None
|
||||
return _im[0]
|
||||
|
||||
def _ocr_dual_engine(self, im_processed: Image.Image, im_original: Image.Image, platform: str) -> tuple:
|
||||
"""双引擎并发 OCR:Windows OCR + EasyOCR 同时跑,最多等 10 秒。
|
||||
|
||||
@@ -1168,8 +1191,9 @@ class MessageMonitor:
|
||||
win_h = bottom - top
|
||||
# QQ NT 窗口布局: ~0-5% 标签栏, ~5-30% 联系人/群列表, ~30-55% 会话列表, ~55-100% 消息面板
|
||||
# 先确保 QQ 可见再截屏,避免 CMD 窗口遮挡→OCR 读到终端文字
|
||||
# sleep 0.4s 让 DWM 完成窗口激活的合成,防止紧接的 BitBlt 卡死
|
||||
self._ensure_visible(hwnd)
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.4)
|
||||
|
||||
variant = self._app_variant.get("qq", "")
|
||||
# TIM (Electron) 安全:不做 PostMessage 点击 + 不做 keybd_event 全局键
|
||||
@@ -1212,7 +1236,9 @@ class MessageMonitor:
|
||||
cap_bottom = bottom - max(100, int(win_h * 0.10)) # 输入框上方
|
||||
if cap_right <= cap_left or cap_bottom <= cap_top:
|
||||
return []
|
||||
im = ImageGrab.grab(bbox=(cap_left, cap_top, cap_right, cap_bottom))
|
||||
im = self._safe_screenshot((cap_left, cap_top, cap_right, cap_bottom))
|
||||
if im is None:
|
||||
return []
|
||||
if im.width < 50 or im.height < 50:
|
||||
return []
|
||||
|
||||
@@ -1308,10 +1334,13 @@ class MessageMonitor:
|
||||
return []
|
||||
|
||||
# 先确保微信可见再截屏,避免终端窗口遮挡 → OCR 读到终端文字
|
||||
# sleep 0.4s 让 DWM 完成窗口激活的合成,防止紧接的 BitBlt 卡死
|
||||
self._ensure_visible(hwnd)
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.4)
|
||||
|
||||
im = ImageGrab.grab(bbox=(cap_left, cap_top, cap_right, cap_bottom))
|
||||
im = self._safe_screenshot((cap_left, cap_top, cap_right, cap_bottom))
|
||||
if im is None:
|
||||
return []
|
||||
if im.width < 50 or im.height < 50:
|
||||
return []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user