死锁案例:UI 线程阻塞等待跨进程 COM 注入

背景

在 Chromium 浏览器中,实现任务栏美化功能需要向 explorer.exe 注入 DLL,通过 XAML TAP(Thread-Aware Provider)接口控制任务栏外观。注入函数 pfnInjectTAP_ 在内部调用 Windows 的 InitializeXamlDiagnosticsEx API 建立跨进程 XAML 诊断连接。


死锁复现

问题代码

初始化路径(CreateWindowOnUIThread,在 UI 线程执行):

复制代码
// 在 UI 线程上调用
void TaskbarHookManager::CreateWindowOnUIThread() {
    // 创建 workerWindow_...

    base::WaitableEvent injection_done(...);

    // 把注入任务派发到 file 线程
    file_task_runner_->PostTask(FROM_HERE,
        base::BindOnce([](TaskbarHookManager* self, base::WaitableEvent* done) {
            self->PerformInjection();   // 内部调用 pfnInjectTAP_
            done->Signal();
        }, this, &injection_done)
    );

    injection_done.Wait();  // ← UI 线程在此阻塞,消息泵停止
}

注入函数(InjectExplorerTAP,在 file 线程执行):

复制代码
// 在 file 线程上执行
HRESULT InjectExplorerTAP(HWND window, REFIID riid, LPVOID* ppv) {
    DWORD tid = GetWindowThreadProcessId(window, &pid);

    // 1. 用 SetWindowsHookEx 将 DLL 注入 explorer 的任务栏线程
    wil::unique_hhook hook(SetWindowsHookEx(WH_CALLWNDPROC, CallWndProc,
                            hModule, tid));

    auto event = TAPSite::GetReadyEvent();

    // 2. 等待 explorer 内 TAP 就绪信号(最多 35 秒)
    if (!event.wait(35000)) {        // ← file 线程在此阻塞
        return HRESULT_FROM_WIN32(WAIT_TIMEOUT);
    }
    ...
}

explorer 侧 TAP 初始化(TAPSite::Install,在 explorer 线程执行):

复制代码
DWORD TAPSite::Install(void*) {
    // 调用 InitializeXamlDiagnosticsEx 建立 XAML 诊断连接
    // 内部通过 COM STA 向调用发起方(Chrome 进程)的 UI 线程发消息进行握手
    hr = ixde(conn.c_str(), pid, nullptr, location.c_str(), ...);
    // 握手完成后才 Signal 就绪事件
    event.SetEvent();
}

死锁闭环

复制代码
Chrome UI 线程
  [状态:WaitableEvent::Wait() 阻塞,消息泵停止]
    │
    │ 等待 file 线程完成注入
    ▼
Chrome file 线程
  [状态:event.wait(35000) 阻塞,等待 explorer TAP 就绪信号]
    │
    │ 等待 explorer 发出 TAP ready event
    ▼
explorer 任务栏线程
  [状态:InitializeXamlDiagnosticsEx 内部通过 COM STA 等待 Chrome UI 线程响应]
    │
    │ 需要 Chrome UI 线程处理 COM 消息(STA 代理/存根握手)
    ▼
Chrome UI 线程
  [消息泵已停止,COM 消息无法分发]
    │
    └── 死锁!三方互等,35 秒后 WAIT_TIMEOUT,注入失败

根本原因

层次 错误
架构层 在 UI 线程上调用阻塞性等待 WaitableEvent::Wait(),违反 Chromium UI 线程不可阻塞原则
系统层 InitializeXamlDiagnosticsEx 基于 COM STA(单线程公寓)机制,跨进程调用需要双方都有活跃的消息泵;UI 线程被阻塞后消息泵停止,COM 握手无法完成
设计层 把依赖 UI 消息泵的跨进程操作(DLL 注入 / COM 初始化)放到需要同步等待的路径上

修复原则

UI 线程永远不能调用阻塞性等待,必须使用以下替代方案:

方案 A:纯异步(最简洁)

复制代码
void TaskbarHookManager::CreateWindowOnUIThread() {
    // 创建 workerWindow_...

    // ✅ 直接 PostTask,不等待,UI 线程立即返回
    if (file_task_runner_) {
        file_task_runner_->PostTask(FROM_HERE,
            base::BindOnce(&TaskbarHookManager::PerformInjection,
                           base::Unretained(this))
        );
    }
    // 不调用 Wait()
}

后续操作(颜色设置等)同样 PostTask 到 file_task_runner_,天然排在注入任务之后,无需等待通知。

方案 B:需要感知完成 ------ 链式 PostTask 回调

复制代码
void TaskbarHookManager::PerformInjection() {
    // ... 注入逻辑 ...

    // 注入完成后,链式回调到 UI 线程
    content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
        base::BindOnce(&TaskbarHookManager::OnInjectionReady,
                       base::Unretained(this))
    );
}

方案 C:必须同步等待 ------ 用 base::RunLoop 替代 WaitableEvent::Wait()

复制代码
// ✅ RunLoop 启动嵌套消息循环,UI 线程仍能处理消息
base::RunLoop run_loop;
file_task_runner_->PostTask(FROM_HERE,
    base::BindOnce([](TaskbarHookManager* m, base::OnceClosure quit) {
        // ... 清理 ...
        std::move(quit).Run();
    }, base::Unretained(this), run_loop.QuitClosure())
);
run_loop.Run();  // 不阻塞消息泵,COM 通信正常

推论:Uninitialize 有完全相同的问题

Uninitialize()taskbarService_->RestoreAllTaskbarsToDefault() 同为 COM STA 调用,在 UI 线程通过 cleanup_done.Wait() 等待,同样会死锁。修复时还需注意资源销毁顺序workerWindow_ 的销毁必须在 file 线程清理(Release + UnloadDLLs)完成后再执行,否则存在并行竞态,应使用链式 PostTask 保证顺序:

复制代码
UI 线程:  PostTask(file, 清理) → 立即返回
                                      ↓
file 线程: COM 还原 → Unhook → UnloadDLLs → PostTask(UI, DestroyWindow)
                                      ↓
UI 线程:  DestroyWindowOnUIThread()   ← 在所有清理完成后才执行

教训

在 UI 线程(或任何有消息泵的线程)上等待依赖消息泵的跨线程/跨进程操作,必然死锁。

COM STA、SetWindowsHookEx、XAML 诊断连接等机制都隐式依赖消息泵,需格外警惕。

相关推荐
煤球王子2 小时前
学而时习之:C++中的预处理
c++
码界奇点2 小时前
基于模块化架构的Unity游戏开发框架设计与实现
java·c++·unity·架构·毕业设计·源代码管理
_饭团2 小时前
指针核心知识:5篇系统梳理2
c语言·笔记·学习·leetcode·面试·改行学it
WangJunXiang62 小时前
Nginx性能优化与监控笔记
笔记·nginx·性能优化
四谎真好看2 小时前
Redis学习笔记(实战篇2)
redis·笔记·学习·学习笔记
阿贾克斯的黎明2 小时前
Drogon 框架完全指南:C++ 后端开发的新选择
开发语言·c++
凯子坚持 c2 小时前
基于C++构建DeepSeek大模型推理SDK:从架构设计到工程落地
java·数据库·c++
郝学胜-神的一滴2 小时前
C++备忘录模式:优雅实现对象状态保存与恢复
开发语言·c++·程序人生·备忘录模式
星河耀银海2 小时前
C++ 异常处理机制:异常捕获、自定义异常与实战应用
android·java·c++