使用Dispatcher.Invoke从非主线程更改WPF控件

91

我最近开始学习WPF编程,并遇到了以下问题。我不明白如何使用Dispatcher.Invoke()方法。我有线程方面的经验,并且写过一些简单的Windows Forms程序,其中我只是使用了

Control.CheckForIllegalCrossThreadCalls = false;

我知道这可能有点糟糕,但它们只是简单的监视应用程序。

事实是,现在我正在制作一个WPF应用程序,在后台检索数据,我启动一个新线程来调用检索数据(从Web服务器),现在我想在我的WPF表单上显示它。问题是,我无法从此线程设置任何控件。甚至连标签都不能。如何解决?

答案评论:
@Jalfp:
那么我应该在“新线程”中使用此Dispatcher方法获取数据吗?还是应该让后台工作程序检索数据,将其放入字段中,并启动一个等待该字段填充并调用Dispatcher以将检索到的数据显示到控件中的新线程?


CheckForIllegalCrossThreadCalls 真是太棒了。希望我之前知道它,可以用于快速的“谁在乎”的应用程序。 - Gaspa79
4个回答

193

首先要明白,Dispatcher不是设计用于运行长时间的阻塞操作(例如从Web服务器检索数据...)。 当您想要运行将在UI线程上执行的操作(例如更新进度条的值)时,可以使用Dispatcher。

您可以在后台工作器中检索数据,并使用ReportProgress方法在UI线程中传播更改。

如果您确实需要直接使用Dispatcher,则非常简单:

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Background,
  new Action(() => this.progressBar.Value = 50));

26
你可以摆脱 'new Action(' 部分,直接使用lambda表达式:DispatcherPriority.Background, () => this.progressBar.Value = 50 - jrista
2
不知道为什么我在这里放了一个Action :p - japf
2
@Carsten 这个回答是针对使用 System.Windows.Application 类的 WPF 应用程序的。 - joshuapoehls
11
@jrista:你真的可以吗?如果没有使用“new Action(...)”,我会得到[CS1660](http://msdn.microsoft.com/en-us/library/hy74she2%28v=vs.80%29.aspx)。 - O. R. Mapper
6
通常情况下是正确的,但这篇文章解释了为什么对于像传递给BeginInvoke的无参方法这样的情况不起作用,而是产生编译器错误CS1660。 - O. R. Mapper
显示剩余5条评论

34

japf已经正确回答了。如果您正在查看多行操作,可以按照以下方式编写。

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Background,
  new Action(() => { 
    this.progressBar.Value = 50;
  }));

其他想了解性能的用户请留意:

如果您的代码需要高性能,您可以首先使用CheckAccess标志来检查是否需要调用。

if(Application.Current.Dispatcher.CheckAccess())
{
    this.progressBar.Value = 50;
}
else
{
    Application.Current.Dispatcher.BeginInvoke(
      DispatcherPriority.Background,
      new Action(() => { 
        this.progressBar.Value = 50;
      }));
}

请注意,方法CheckAccess()在Visual Studio 2015中已隐藏,因此请在不指望Intellisense显示的情况下编写它。请注意,CheckAccess会对性能产生开销(几纳秒的开销)。只有在您想要以任何代价节省执行“调用”所需的微秒时,才更好。此外,如果调用方法确定它是否在UI线程中,始终可以选择创建两个方法(一个带调用,另一个不带)。只有在最罕见的情况下,您才应该关注dispatcher的这个方面。


7

当一个线程正在执行,并且您希望执行被当前线程阻塞的主UI线程时,请使用以下方法:

当前线程:

Dispatcher.CurrentDispatcher.Invoke(MethodName,
    new object[] { parameter1, parameter2 }); // if passing 2 parameters to method.

主 UI 线程:

Application.Current.Dispatcher.BeginInvoke(
    DispatcherPriority.Background, new Action(() => MethodName(parameter)));

MethodName在当前上下文中不存在。 - AriesConnolly
1
@AriesConnolly,“MethodName”将会被替换成你想要调用的方法名。 - Dru Steeby

0

@japf的答案很好用,在我的情况下,我想在CEF浏览器加载完页面后将鼠标光标从旋转齿轮更改回正常的箭头。如果可以帮助到别人,这是代码:

private void Browser_LoadingStateChanged(object sender, CefSharp.LoadingStateChangedEventArgs e) {
   if (!e.IsLoading) {
      // set the cursor back to arrow
      Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
         new Action(() => Mouse.OverrideCursor = Cursors.Arrow));
   }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接