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

Posted

技术标签:

【中文标题】使用 Dispatcher.Invoke 从非主线程更改 WPF 控件【英文标题】:Change WPF controls from a non-main thread using Dispatcher.Invoke 【发布时间】:2010-12-11 06:33:31 【问题描述】:

我最近开始使用 WPF 进行编程,遇到了以下问题。我不明白如何使用Dispatcher.Invoke() 方法。我有线程方面的经验,并且我制作了一些简单的 Windows 窗体程序,我刚刚使用了

Control.CheckForIllegalCrossThreadCalls = false;

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

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

回答 cmets: @Jalfp:所以当我获取数据时,我在“新胎面”中使用了这个 Dispatcher 方法?或者我应该让后台工作人员检索数据,将其放入一个字段并启动一个新线程,等待该字段被填充并调用调度程序将检索到的数据显示到控件中?

【问题讨论】:

CheckForIllegalCrossThreadCalls 太棒了。希望我之前知道快速“谁在乎”应用程序 【参考方案1】:

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

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

如果你真的需要直接使用 Dispatcher,其实很简单:

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

【讨论】:

你可以去掉 'new Action(' 部分,直接使用 lambda 表达式:DispatcherPriority.Background, () => this.progressBar.Value = 50 是的,不知道为什么我在这里放一个动作:p @Carsten 这个答案适用于使用 System.Windows.Application 类的 WPF 应用程序。 @jrista:你真的可以吗?尝试不使用new Action(...) 时,我得到CS1660。 @jrista:一般来说,是的——尽管this article 解释了为什么它在传递给BeginInvoke 的无参数方法的情况下不起作用,而是产生了编译器错误CS1660。 【参考方案2】:

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 中是隐藏的,因此只需编写它,不要期望智能感知显示它。请注意,CheckAccess 在性能上有开销(开销在几纳秒内)。只有当您想不惜一切代价节省执行“调用”所需的微秒时,它才会更好。此外,当调用方法确定它是否在 UI 线程中时,总是可以选择创建两个方法(使用调用,其他不使用)。当您应该查看调度程序的这方面时,这只是最罕见的情况。

【讨论】:

【参考方案3】:

当一个线程正在执行并且你想要执行被当前线程阻塞的主 UI 线程时,请使用以下:

当前线程:

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

主界面线程:

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

【讨论】:

MethodName 在当前上下文中不存在 @AriesConnolly MethodName 将被您尝试调用的方法替换【参考方案4】:

上面的@japf 答案工作正常,在我的情况下,我想将鼠标光标从 Spinning Wheel 更改回正常的 Arrow 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));
   

【讨论】:

以上是关于使用 Dispatcher.Invoke 从非主线程更改 WPF 控件的主要内容,如果未能解决你的问题,请参考以下文章

在 Dispatcher.Invoke() 中使用 lambda 表达式作为参数

等待 Dispatcher.InvokeAsync 与 Dispatcher.Invoke

Application.Current.Dispatcher.BeginInvoke(action) VS。 Application.Current.Dispatcher.Invoke(action)

我怎样才能给 Dispatcher.Invoke 一个论点?

如何使用C#的方法 Dispatcher.Invoke =>

C# Dispatcher Invoke 和正常代码执行顺序