使用 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 在当前上下文中不存在 @AriesConnollyMethodName
将被您尝试调用的方法替换【参考方案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 一个论点?