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

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

Control.CheckForIllegalCrossThreadCalls = false;

是的,我知道这是相当蹩脚,但这些是简单的监视应用程序。

事实上,现在我正在做一个 WPF 应用程序,它在后台检索数据,我开始了一个新的线程,使调用检索数据(从一个网络服务器) ,现在我想显示它在我的 WPF 表单。问题是,我无法从这个线程设置任何控件。连个标签都没有。这个问题怎么解决?

回答评论:
@ Jalfp:
所以当我得到数据时,我在“新胎面”中使用这个 Dispatcher 方法?或者我应该让后台工作人员检索数据,把它放到一个字段中,然后启动一个新的线程,直到这个字段被填满,然后调用调度程序将检索到的数据显示到控件中?

238823 次浏览

The first thing is to understand that, the Dispatcher is not designed to run long blocking operation (such as retrieving data from a WebServer...). You can use the Dispatcher when you want to run an operation that will be executed on the UI thread (such as updating the value of a progress bar).

What you can do is to retrieve your data in a background worker and use the ReportProgress method to propagate changes in the UI thread.

If you really need to use the Dispatcher directly, it's pretty simple:

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

japf has answer it correctly. Just in case if you are looking at multi-line actions, you can write as below.

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

Information for other users who want to know about performance:

If your code NEED to be written for high performance, you can first check if the invoke is required by using CheckAccess flag.

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

Note that method CheckAccess() is hidden from Visual Studio 2015 so just write it without expecting intellisense to show it up. Note that CheckAccess has overhead on performance (overhead in few nanoseconds). It's only better when you want to save that microsecond required to perform the 'invoke' at any cost. Also, there is always option to create two methods (on with invoke, and other without) when calling method is sure if it's in UI Thread or not. It's only rarest of rare case when you should be looking at this aspect of dispatcher.

When a thread is executing and you want to execute the main UI thread which is blocked by current thread, then use the below:

current thread:

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

Main UI thread:

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

The @japf answer above is working fine and in my case I wanted to change the mouse cursor from a Spinning Wheel back to the normal Arrow once the CEF Browser finished loading the page. In case it can help someone, here is the code:

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