WPF 调度程序执行多个执行路径
好吧,周末我发现了一些奇怪的事情。我有一个 WPF 应用程序,它会生成一些线程来执行后台工作。然后,这些后台线程将工作项发布到我的同步上下文。除了一种情况外,一切都工作正常。当我的线程完成时,有时它们会向调度程序发布一个操作,该操作将打开一个弹出窗口。最终发生的情况是,如果两个线程都在调度程序上发布一个操作,它就会开始处理一个操作,然后如果我使用 Window.ShowDialog() 打开一个弹出窗口;当前执行路径按其应有的方式暂停,等待来自对话框的反馈。但问题出现了,当对话框打开时,调度程序立即开始运行发布的第二个操作。这会导致执行两个代码路径。第一个消息框保持打开状态,而第二个消息框正在疯狂运行,因为我的应用程序状态未知,因为第一个操作从未完成。
我发布了一些示例代码来演示我正在讨论的行为。应该发生的情况是,如果我发布 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() 停止执行时一次只能处理一个操作?
谢谢, 劳尔
Ok, so I found something weird over the weekend. I have a WPF app that spawns off some threads to perform background work. Those background threads then Post work items to my Synchronization Context. This is all working fine except for one case. When my threads finish sometimes they will post an action onto the dispatcher that will open up a Popup window. What ends up happening is that if 2 threads both post an action on the Dispatcher it starts processing one, then if I open up a Popup window with Window.ShowDialog(); the current execution path pauses waiting for feedback from the dialog box as it should. But the problem arises that when the dialog box opens the Dispatcher then goes and starts immediately starts running the second action that was posted. This results in two code paths being executed. The first one with a message box being held open, while the second one is running wild because my application state is unknown because the first action never completed.
I've posted some example code to demonstrate the behavior I'm talking about. What should happen is that if I post 2 actions and the 1st one opens up a dialog box the second action shouldn't run until after the 1st action has been completed.
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);
}
}
The XAML is just a Window with a button that calls Button_ClickWithout OnClick. If you push the button twice and wait 3 seconds, you will see you get 2 dialogs popping up one over the other, where the expected behavior would be the first one pops up, then once it's closed the second one will pop up.
So my question is: Is this a bug ? or how do I mitigate this so I can have only one action be processed at a time when the first action halts execution with a Window.ShowDialog() ?
Thanks,
Raul
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
当我正在等待我的问题的答案时(有关使用调度程序的建议优先级和绑定)我认为这会向前支付™。
您正在经历的是调度程序上的嵌套泵送。我建议阅读有关 WPF 线程模型 的 MSDN 文章,尤其是标题为“技术细节和绊脚石”的部分位于页面下方的三分之二处。为了方便起见,描述嵌套泵送的小节复制如下。
As I'm awaiting an answer on my question (Advice on using the Dispatcher Priority and Binding) I thought that would pay-it-forward™.
What you are experiencing is Nested Pumping on the Dispatcher. I recommend reading the MSDN article on the WPF Threading Model, especially the section titled 'Technical Details and Stumbling Points' that is two-thirds down the page. The sub-section describing the Nested Pumping is copied below for convenience.
模式对话框不会阻止所有者窗口处理消息,否则当模式对话框在其表面上移动时,您会看到它无法重绘(仅作为示例)。
为了实现你想要的,你必须在 UI 线程上实现你自己的队列,可能需要一些同步来在第一个工作项到达时“唤醒它”。
编辑:
此外,如果您在第二个模式对话框启动时检查 UI 线程的调用堆栈,您可能会发现它在堆栈中的上方有第一个 ShowDialog 调用。
编辑#2:
可能有一种更简单的方法可以做到这一点,而无需实现您自己的队列。如果您使用
Dispatcher
对象而不是SynchronizationContext
,您将能够以优先级调用
,它将正确排队(检查)。BeginInvoke
。 DispatcherPriority.NormalA modal dialog box does not prevent the owner window from processing messages, otherwise you'd see it fail to redraw as the modal dialog was moved over its surface (just as an example).
In order to achieve what you want, you have to implement your own queue on the UI thread, possibly with some synchronization to "wake it up" when the first work item arrives.
EDIT:
Also if you examine the call stack of the UI thread while the 2nd modal dialog box is up, you might find out that it has the first ShowDialog call above it in the stack.
EDIT #2:
There might be an easier way of doing this, without implementing your own queue. If instead of the
SynchronizationContext
, you would use theDispatcher
object, you would be able to callBeginInvoke
on it with a priority ofDispatcherPriority.Normal
, and it will get queued properly (check).