【问题标题】:WPF Dispatcher executing multiple execution pathsWPF Dispatcher 执行多个执行路径
【发布时间】:2010-05-03 17:01:13
【问题描述】:

好的,所以我在周末发现了一些奇怪的东西。我有一个 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() 停止执行时一次只能处理一个操作?

谢谢, 劳尔

【问题讨论】:

    标签: c# wpf debugging dispatcher


    【解决方案1】:

    当我在等待我的问题 (Advice on using the Dispatcher Priority and Binding) 的答案时,我认为这将是 pay-it-forward™。

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

    嵌套抽水

    有时完全锁定 UI 线程是不可行的。 让我们考虑 MessageBox 类的 Show 方法。显示不 返回,直到用户单击 OK 按钮。但是,它确实创建了一个 必须具有消息循环才能交互的窗口。尽管 我们在等待用户点击OK,原来的应用 窗口不响应用户输入。然而,它确实继续 处理油漆消息。原始窗口重绘自身时 遮盖又显露。

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

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

    【讨论】:

      【解决方案2】:

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

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

      编辑:

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

      编辑#2:

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

      【讨论】:

      • 我确实创建了自己的同步上下文队列,但我一直在寻找更优雅的解决方案。我尝试使用优先级为 Normal 的调度程序,但得到了相同的结果。我想在一天结束时,我只是将我的 UI 更改为不使用模式对话框。你会认为会有一些方法可以解决这个问题,但我猜不是。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-23
      • 2016-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-01
      相关资源
      最近更新 更多