2010-05-03 65 views
0

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

我已经发布了一些示例代码来演示我正在谈论的行为。应该发生的是,如果我发布了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秒钟,你会看到你有两个对话框弹出另一个对话框,其中预期的行为将是第一个对话框弹出,然后一旦它关闭,第二个对话框将弹出。

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

谢谢,劳尔

回答

0

一个模式对话框不会阻止处理邮件所有者窗口,否则你会看到它不能重绘模态对话框被移动了在其表面(就像一个例子) 。

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

编辑:

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

编辑#2:

有可能是这样,没有实现自己的队列更简单的方法。如果使用SynchronizationContext而不是SynchronizationContext,则可以使用Dispatcher对象,您可以调用BeginInvoke,优先级为DispatcherPriority.Normal,它将正确排队(检查)。

+0

我确实创建了自己的同步上下文队列,但我一直在寻找更优雅的解决方案。我尝试使用优先级为Normal的调度程序,但得到了相同的结果。我想在一天结束的时候我会改变我的UI,不使用模态对话框。你会认为会有某种方法可以解决这个问题,但我不会。 – HaxElit 2010-05-06 14:25:50

0

我知道这是一个老问题,但因为我在等待我的问题的答案(Advice on using the Dispatcher Priority and Binding)我认为这将付出代价。

您遇到的情况是调度器上的嵌套抽取。我建议阅读WPF Threading Model上的MSDN文章,尤其是标题为“技术细节和障碍点”的部分,该部分是页面下方的三分之二。描述嵌套抽吸的小节在下面被复制以方便起见。

嵌套抽水

有时候是不可行的完全锁定了UI线程。 让我们考虑一下MessageBox类的Show方法。显示不会 返回,直到用户单击确定按钮。但是,它会创建一个 窗口,该窗口必须具有消息循环才能进行交互。虽然 我们正在等待用户单击确定,原始应用程序 窗口不响应用户输入。但是,它会继续处理画图消息。当 覆盖并显示时,原始窗口会自行重绘。

enter image description here

一些线程必须负责消息框窗口。 WPF 只为消息框窗口创建一个新线程,但是此线程 将无法​​在原始窗口 (请记住先前对互斥的讨论)中绘制禁用的元素。相反,WPF 使用嵌套消息处理系统。 Dispatcher类包含 一种称为PushFrame的特殊方法,它存储应用程序的当前执行点,然后开始一个新的消息循环。当 嵌套消息循环完成后,将在原始的 PushFrame调用后继续执行。

在这种情况下,PushFrame在调用 MessageBox.Show时维护程序上下文,并启动一个新的消息循环来重新绘制背景窗口并处理消息框窗口的输入。当 用户单击“确定”并清除弹出窗口时,嵌套循环退出,并在调用“显示”后继续执行 控制。