WPF中的死锁:如何在HwndHost.BuildWindowCore中不阻塞GUI线程?

4
我们有一个用C++和Qt处理和可视化图像的大型代码库。现在有用户想要在此基础上构建,但他们的代码库是.NET Core 3.1和WPF。我们使用PInvoke来接口本地代码,并可以非常成功地加入项目。少量本地小部件嵌入了Win32-in-WPF-HwndHost包装器。一切都运作得很好,我们对所取得的进展感到非常满意!
只有一个阻塞问题: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线程?
1个回答

2
您的WPF泵(Dispatcher)被阻塞了。异步/等待/Yield将无法帮助您,因为您不在异步函数中。
当您处于BuildWindowCore()内部时,需要WPF保持泵送。您可以通过在不同线程上调用本机代码,然后使WPF线程坐下并泵送消息,直到工作线程完成来实现此目的。您可以使用 Dispatcher.PushFrame来完成此操作。
希望类似于以下内容:
protected override HandleRef BuildWindowCore(HandleRef HWNDParent)
{
    // call into QT in worker thread
    var reParentTask = Task.Run(() => NativeCode.beginReParenting(HWNDParent.Handle));

    // pump messages while we wait for QT to do its stuff
    var frame = new DispatcherFrame();
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
        new DispatcherOperationCallback(delegate (object f)
            {
                ((DispatcherFrame)f).Continue = !reParentTask.IsCompleted;
                return null;
            }),frame);

    Dispatcher.PushFrame(frame);

    return new HandleRef(this, NativeCode.getEmbeddedWidgetHandle());
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接