WPF Dispatcher执行多个执行路径

时间:2010-05-03 17:01:14

标签: c# wpf debugging dispatcher

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

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

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秒钟,您将看到有两个对话框突然出现在另一个对话框中,其中预期的行为将是第一个弹出,然后一旦关闭,第二个将弹出。

所以我的问题是:这是一个错误吗?或者我该如何缓解这个问题,这样当第一个动作停止执行Window.ShowDialog()时,我只能处理一个动作?

谢谢, 劳尔

2 个答案:

答案 0 :(得分:0)

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

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

编辑:

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

编辑#2:

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

答案 1 :(得分:0)

我知道这是一个老问题,但是我在等待我的问题答案(Advice on using the Dispatcher Priority and Binding)我认为这会付费转发™。

您遇到的问题是Dispatcher上的 Nested Pumping 。我建议阅读WPF Threading Model上的MSDN文章,尤其是标题为“技术细节和绊脚点”的部分,该部分是页面的三分之二。为方便起见,下面复制了描述嵌套泵送的子部分。

  

嵌套抽水

     

有时完全锁定UI线程是不可行的。   让我们考虑一下MessageBox类的Show方法。显示没有   返回,直到用户单击“确定”按钮。然而,它确实创造了一个   必须具有消息循环以便交互的窗口。而   我们正在等待用户单击确定,原始应用程序   窗口不响应用户输入。但是,它仍然继续   处理油漆消息。原始窗口何时重绘   覆盖和透露。

     

enter image description here

     

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

     

在这种情况下,PushFrame会在调用时维护程序上下文   MessageBox.Show,它启动一个新的消息循环来重绘   背景窗口和句柄输入到消息框窗口。当。。。的时候   用户单击确定并清除弹出窗口,嵌套循环退出和   控制在Show。

之后恢复