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


81

我最近开始在WPF中编程,遇到了以下问题。我不知道如何使用该Dispatcher.Invoke()方法。我在线程方面有丰富的经验,我制作了一些简单的Windows Forms程序,其中我只是使用了

Control.CheckForIllegalCrossThreadCalls = false;

是的,我知道这很la脚,但是这些都是简单的监视应用程序。

事实是,现在我正在制作一个WPF应用程序,该应用程序在后台检索数据,我从一个新线程开始进行调用以(从Web服务器)检索数据,现在我想在WPF表单上显示它。问题是,我无法从该线程设置任何控件。甚至没有标签或其他任何东西。如何解决?

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


CheckForIllegalCrossThreadCalls很棒。希望我早就知道要进行快速的“谁在乎”应用程序
Gaspa79

Answers:


177

首先要了解的是,Dispatcher并非旨在运行长时间阻塞操作(例如从WebServer检索数据...)。当您要运行将在UI线程上执行的操作(例如更新进度条的值)时,可以使用Dispatcher。

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

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

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

22
您可以摆脱'new Action(')部分,而只需使用一个lambda表达式:DispatcherPriority.Background,()=> this.progressBar.Value = 50
jrista

是的,不知道为什么我要在这里采取行动:p
japf 2009年

1
@Carsten此答案适用于使用System.Windows.Application类的WPF应用程序。
joshuapoehls

10
@jrista:你真的可以吗?如果没有尝试,我会得到CS1660new Action(...)
OR Mapper

6
@jrista:通常,这是正确的-尽管本文解释了为什么在无参数方法(例如传递给方法)的情况下它不起作用,但BeginInvoke会产生编译器错误CS1660。
OR Mapper

31

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;
      }));
}

请注意,Visual Studio 2015中隐藏了CheckAccess()方法,因此只需编写它而不希望显示它的智能。请注意,CheckAccess具有性能开销(开销在几纳秒内)。仅当您想节省执行“调用”所需的微秒时,这才更好。另外,在确定方法是否在UI Thread中时,总是有一个选项来创建两个方法(带调用,带调用,其他不带)。当您应该关注调度程序的这一方面时,这是最罕见的情况。


4

当线程正在执行并且您想要执行被当前线程阻止的主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

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));
   }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.