【发布时间】:2021-07-16 19:50:29
【问题描述】:
我想了解以下行为。
我有带有按钮单击事件处理程序的 WPF 应用程序,我在其中启动 Parallel.ForEach。在每个循环中,我通过 Dispatcher 更新 UI。在Parallel.Foreach 之后,我对 UI 进行了“最终更新”。然而,这个“最终更新”实际上发生在来自Dispatcher 的任何更新之前。为什么会按这个顺序发生?
private void btnParallel_Click(object sender, RoutedEventArgs e)
{
txbResultsInfo.Text = "";
Parallel.ForEach(_files, (file) =>
{
string partial_result = SomeComputation(file);
Dispatcher.BeginInvoke(new Action(() =>
{
txbResultsInfo.Text += partial_result + Environment.NewLine;
}));
});
txbResultsInfo.Text += "-- COMPUTATION DONE --"; // THIS WILL BE FIRST IN UI, WHY?
//Dispatcher.BeginInvoke(new Action(() => txbResultsInfo.Text += "-- COMPUTATION DONE --"; - THIS WAY IT WILL BY LAST IN UI
}
我的直觉期望是代码在 Parallel.ForEach 循环的所有分支完成后继续,这意味着 Dispatcher 已收到所有 UI 更新请求并开始执行它们,然后我们才继续更新 UI 从处理程序方法的其余部分。
但"-- COMPUTATION DONE --" 实际上总是首先出现在 textBlock 中。即使我将Task.Delay(5000).Wait() 放在“完成计算”更新之前。因此,这不仅仅是速度问题,实际上以某种方式排序,此更新发生在 Dispatcher 更新之前。
如果我将“完成计算”更新也放入调度程序,它的行为与我预期的一样,并且位于文本的末尾。但是为什么这也需要通过 dispatcher 来完成呢?
【问题讨论】:
-
这确实很奇怪。通常
Parallel.ForEach方法应该等到一切都完成然后再进一步......也许如果你在foreach 的末尾添加.IsCompleted? (虽然我认为这不会解决它) -
我会指出,由于这是 WPF 代码,因此您的代码直接设置 UI 属性无论如何都是错误的。您应该使用数据绑定,并让 WPF 框架处理跨线程调度。这不一定会改变您在上面看到的内容,但至少会是更好的代码。
-
BeginInvoke()不会等待。它只是排队你派出的委托并返回。在您的示例中,所有已调度的委托都由Parallel.ForEach()排队,这会阻塞 UI 线程,否则会调用排队的委托,因此该方法返回并让对txbResultsInfo.Text的分配发生在任何排队的委托到达之前实际上被称为。有关详细信息,请参阅副本(所有BeginXXX()与XXX()方法都以这种方式工作)。
标签: c# wpf dispatcher parallel.foreach