0

好的,所以我在周末发现了一些奇怪的东西。我有一个 WPF 应用程序,它产生一些线程来执行后台工作。然后这些后台线程将工作项发布到我的同步上下文中。除了一种情况外,这一切都很好。当我的线程完成时,有时他们会在调度程序上发布一个操作,这将打开一个弹出窗口。最终发生的是,如果 2 个线程都在 Dispatcher 上发布一个动作,它开始处理一个,然后如果我用 Window.ShowDialog(); 打开一个弹出窗口;当前执行路径暂停等待对话框的反馈。但是问题出现了,当对话框打开时,Dispatcher 然后开始并立即开始运行已发布的第二个操作。这导致两个代码路径被执行。

我已经发布了一些示例代码来演示我正在谈论的行为。应该发生的是,如果我发布了 2 个操作并且第一个操作打开了一个对话框,那么在第一个操作完成之前,第二个操作不应该运行。

public partial class Window1 : Window {

    private SynchronizationContext syncContext;
    public Window1() {
        InitializeComponent();
        syncContext = SynchronizationContext.Current;
    }

    private void Button_ClickWithout(object sender, RoutedEventArgs e) {
        // Post an action on the thread pool with the syncContext
        ThreadPool.QueueUserWorkItem(BackgroundCallback, syncContext);
    }

    private void BackgroundCallback(object data) {
        var currentContext = data as SynchronizationContext;

        System.Console.WriteLine("{1}: Thread {0} started", Thread.CurrentThread.ManagedThreadId, currentContext);

        // Simulate work being done
        Thread.Sleep(3000);

        currentContext.Post(UICallback, currentContext);

        System.Console.WriteLine("{1}: Thread {0} finished", Thread.CurrentThread.ManagedThreadId, currentContext);
    }

    private void UICallback(object data) {
        System.Console.WriteLine("{1}: UI Callback started on thread {0}", Thread.CurrentThread.ManagedThreadId, data);

        var popup = new Popup();

        var result = popup.ShowDialog();

        System.Console.WriteLine("{1}: UI Callback finished on thread {0}", Thread.CurrentThread.ManagedThreadId, data);
    }
}

XAML 只是一个带有调用 Button_ClickWithout OnClick 的按钮的窗口。如果您按下按钮两次并等待 3 秒,您将看到 2 个对话框一个接一个地弹出,其中预期的行为将是第一个弹出,然后一旦关闭,第二个将弹出。

所以我的问题是:这是一个错误吗?或者我该如何缓解这种情况,以便在第一个操作使用 Window.ShowDialog() 停止执行时,我只能处理一个操作?

谢谢,劳尔

4

2 回答 2

1

当我在等待我的问题的答案(关于使用调度程序优先级和绑定的建议)时,我认为这将支付它转发™。

您正在经历的是调度程序上的嵌套抽水。我建议阅读有关WPF 线程模型的 MSDN 文章,尤其是页面下方三分之二的标题为“技术细节和绊脚石”的部分。为方便起见,下面复制了描述嵌套抽水的小节。

嵌套抽水

有时完全锁定 UI 线程是不可行的。让我们考虑 MessageBox 类的 Show 方法。在用户单击 OK 按钮之前,Show 不会返回。但是,它确实创建了一个必须具有消息循环才能进行交互的窗口。当我们等待用户点击 OK 时,原来的应用程序窗口没有响应用户输入。但是,它确实会继续处理绘制消息。原始窗口在被覆盖和显示时会重新绘制。

在此处输入图像描述

必须有某个线程负责消息框窗口。WPF 可以为消息框窗口创建一个新线程,但是该线程将无法在原始窗口中绘制禁用的元素(请记住前面关于互斥的讨论)。相反,WPF 使用嵌套的消息处理系统。Dispatcher 类包含一个称为 PushFrame 的特殊方法,它存储应用程序的当前执行点,然后开始一个新的消息循环。当嵌套消息循环完成时,执行在原始 PushFrame 调用之后恢复。

在这种情况下,PushFrame 在调用 MessageBox.Show 时维护程序上下文,并启动一个新的消息循环以重新绘制背景窗口并处理消息框窗口的输入。当用户单击 OK 并清除弹出窗口时,嵌套循环退出并在调用 Show 后恢复控制。

于 2011-09-19T20:52:53.820 回答
0

模态对话框不会阻止所有者窗口处理消息,否则当模态对话框在其表面上移动时,您会看到它无法重绘(仅作为示例)。

为了实现你想要的,你必须在 UI 线程上实现你自己的队列,可能有一些同步以在第一个工作项到达时“唤醒它”。

编辑:

此外,如果您在第二个模式对话框启动时检查 UI 线程的调用堆栈,您可能会发现它在堆栈中具有第一个 ShowDialog 调用。

编辑#2:

可能有一种更简单的方法可以做到这一点,而无需实现您自己的队列。如果SynchronizationContext您将使用该Dispatcher对象而不是 ,您将能够以BeginInvoke优先级调用它DispatcherPriority.Normal,并且它将正确排队(检查)。

于 2010-05-03T17:30:31.510 回答