我们有一个用C++和Qt处理和可视化图像的大型代码库。现在有用户想要在此基础上构建,但他们的代码库是.NET Core 3.1和WPF。我们使用PInvoke来接口本地代码,并可以非常成功地加入项目。少量本地小部件嵌入了Win32-in-WPF-HwndHost包装器。一切都运作得很好,我们对所取得的进展感到非常满意!
只有一个阻塞问题:HwndHost方法BuildWindowCore有时会陷入死锁。我们可以确定死锁的根本原因:
当BuildWindowCore调用Qt将本地小部件重新父级到托管小部件的句柄中时,我们使用阻塞调用以确保重新父级完成。然而,在重新父级小部件期间,Qt有时会调用DefWindowProc将未处理的窗口消息传递回父WPF小部件。由于WPF线程被阻塞调用Qt,这是一个循环阻塞等待,最终导致死锁。
只有一个阻塞问题:HwndHost方法BuildWindowCore有时会陷入死锁。我们可以确定死锁的根本原因:
当BuildWindowCore调用Qt将本地小部件重新父级到托管小部件的句柄中时,我们使用阻塞调用以确保重新父级完成。然而,在重新父级小部件期间,Qt有时会调用DefWindowProc将未处理的窗口消息传递回父WPF小部件。由于WPF线程被阻塞调用Qt,这是一个循环阻塞等待,最终导致死锁。
虽然我能理解这个问题,但我们对 WPF GUI 线程的了解不足以解决这个问题。
到目前为止,我们尝试过:
- 在后台使用
await
调用 Qt,但是BuildWindowCore
不能是异步方法。 - 将重新父级操作移出
BuildWindowCore
,稍后再调用它作为async
,但是HwndHost
需要重新父级操作在BuildWindowCore
中进行,否则我们会得到一个 WPF 错误,即本地部件句柄尚未成为 WPF 部件的子部件。
我有点束手无策。我们可以在 C++ 端将对本机代码的调用设为非阻塞,并在 C# 中循环轮询其完成情况。但是,在我们轮询 Qt 重新父级操作小部件时,我们如何将控制权交还给 WPF GUI 线程?
最相关的 SO answer 建议使用 await Dispatcher.Yield(DispatcherPriority.ApplicationIdle)
,但这是在一个 async
方法中。
我在想的伪代码大致如下:
protected override HandleRef BuildWindowCore(HandleRef HWNDParent)
{
NativeCode.beginReParenting(HWNDParent.Handle);
while (!NativeCode.reParentingCompleted()) {
// This method is async, is it clean to call it like this?
Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
}
return new HandleRef(this, NativeCode.getEmbeddedWidgetHandle());
}
我的问题:
- Dispatcher.Yield()(或类似概念)在轮询循环中有助于保持WPF的响应性吗?
- 当
BuildWindowCore
本身无法异步时,我们如何清晰地调用GUI线程中的async Dispatcher.Yield()
方法? - 这是一个“好”的解决方案,还是有更好的方法在调用
HwndHost.BuildWindowCore()
时不阻塞WPF GUI线程?