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。

之后恢复
相关问题