wpf C#、MVVM、任务和 UI 线程

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21453501/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 10:40:12  来源:igfitidea点击:

C#, MVVM, Tasks and the UI Thread

c#wpfmultithreadingmvvmtask

提问by Chris

We have an application built according to the MVVM pattern. At various times we kick off tasks to go to a database to retrieve data, we then populate an ObservableCollection to which a WPF control is bound with that data.

我们有一个根据 MVVM 模式构建的应用程序。在不同时间,我们启动任务以转到数据库以检索数据,然后我们填充一个 ObservableCollection,WPF 控件与该数据绑定到该 ObservableCollection。

We are a little confused, when we populate the ObservableCollection we are doing so on the task thread, not the UI thread, yet the UI is still updated/behaving correctly. We were expecting an error and to have to change the code to populate the collection on the UI thread.

我们有点困惑,当我们填充 ObservableCollection 时,我们是在任务线程而不是 UI 线程上执行此操作,但 UI 仍然更新/行为正确。我们预计会出现错误并且必须更改代码以在 UI 线程上填充集合。

Is this a dangerous scenario and should we populate on the UI thread anyway?

这是一个危险的场景,我们应该在 UI 线程上填充吗?

Code to get data:

获取数据的代码:

Task.Factory.StartNew(() =>
    UiDataProvider.RefreshForwardContractReport(fcrIdField.Value)
)
.ContinueWith(task => {
    if (task.Result.OperationSuccess)
    {
        // This updates the ObseravableCollection, should it be run on UI thread??
        RefreshReport(task.Result.OperationResult);
    }
});

采纳答案by Cameron MacFarland

There could be any number of reasons why the continuation is running on the UI thread. The MVVM framework could be helping, or something else is making it run on the UI thread, or you're just getting lucky.

延续在 UI 线程上运行的原因可能有多种。MVVM 框架可能会有所帮助,或者其他东西使它在 UI 线程上运行,或者您只是很幸运。

To ensure the continuation runs on the UI thread you can capture the UI TaskSchedulerright before like so.

为了确保继续在 UI 线程上运行,您可以TaskScheduler像这样捕获 UI 。

var uiScheduler = TaskScheduler.FromCurrentSyncronizationContext();

Task.Factory.StartNew(() =>
    UiDataProvider.RefreshForwardContractReport(fcrIdField.Value),
    TaskCreationOptions.LongRunning
) // Ensures the task runs in a new thread
.ContinueWith(task => {
    if (task.Result.OperationSuccess)
    {
        RefreshReport(task.Result.OperationResult);
    }
}, uiScheduler); // Runs the continuation on the UI thread.

This assumes that the outer method is run from the UI to begin with. Otherwise you could capture the UI scheduler at a top level and access it globally in your app.

这假设外部方法从 UI 开始运行。否则,您可以在顶层捕获 UI 调度程序并在您的应用程序中全局访问它。

If you can use async/await then the code becomes much easier.

如果您可以使用 async/await,那么代码就会变得容易得多。

var result = await Task.Factory.StartNew(
    () => UiDataProvider.RefreshForwardContractReport(fcrIdField.Value),
    TaskCreationOptions.LongRunning
);

if (result.OperationSuccess)
{
    RefreshReport(result.OperationResult);
}

回答by Stephen Cleary

WPF keeps allowing more cross-thread operations with each release. Other MVVM platforms do not allow them at all. In the case of ObservableCollection, unless you are using EnableCollectionSynchronization, updating from a background thread is incorrect.

WPF 在每个版本中都允许更多的跨线程操作。其他 MVVM 平台根本不允许它们。在 的情况下ObservableCollection,除非您正在使用 ,否则EnableCollectionSynchronization从后台线程更新是不正确的。

I agree with @avo in that you should treat your ViewModel (your logicalUI) as though it had UI thread affinity (like the literalUI). All data binding updates should be done in the UI context.

我同意 @avo 的观点,因为您应该将 ViewModel(您的逻辑UI)视为具有 UI 线程关联性(如文字UI)。所有数据绑定更新都应在 UI 上下文中完成。

As @Cameron pointed out, this is most easily done via async:

正如@Cameron 指出的,这最容易通过async以下方式完成:

var result = await Task.Run(
    () => UiDataProvider.RefreshForwardContractReport(fcrIdField.Value));
if (result.OperationSuccess)
{
  RefreshReport(result.OperationResult);
}

回答by StepUp

It happens because WPF automatically dispatches the PropertyChanged event to the main thread, unlike all other XAML frameworks. In all other frameworks, a dispatching solution is needed. The best article I've read about MVVM and multi threading https://msdn.microsoft.com/en-us/magazine/dn630646.aspx

发生这种情况是因为与所有其他 XAML 框架不同,WPF 会自动将 PropertyChanged 事件调度到主线程。在所有其他框架中,都需要调度解决方案。我读过的关于 MVVM 和多线程的最佳文章 https://msdn.microsoft.com/en-us/magazine/dn630646.aspx

回答by Mayur Dhingra

Considering the MVVM scenario, when you are in the ContinueWith part, i agree that you are in the Non-UI thread and you are updating the properties in the ViewModel (which are bound to UI elemnts) and not the UI elements itself.

考虑到 MVVM 场景,当您处于 ContinueWith 部分时,我同意您处于非 UI 线程中并且您正在更新 ViewModel 中的属性(绑定到 UI 元素)而不是 UI 元素本身。

Try updating the UI elements in the ContinueWith part, say like

尝试更新 ContinueWith 部分中的 UI 元素,例如

.ContinueWith(task => myTextblock.Text = "SomeText")

In that scenario, u'll get the exception that you are expecting.

在这种情况下,您会得到您期望的异常。

P.S - "myTextBlock" is a text block in your View.

PS - “myTextBlock”是您视图中的文本块。

回答by Sukram

If you only change your date in the ViewModel, there should be no problem. The ui updates will be raised using INotifyPropertyChanged in your view model.

如果只在 ViewModel 中更改日期,应该没有问题。ui 更新将在您的视图模型中使用 INotifyPropertyChanged 引发。

I prefer the writing of async and await instead of continue with. For most collegues it is more readable. But it is only code sugering, so there will be no different to you implementation.

我更喜欢写 async 和 await 而不是 continue 。对于大多数同事来说,它更具可读性。但这只是代码修改,因此您的实现不会有什么不同。

回答by Roman Izosimov

You should use another (not UI) thread to retrieve long data from DB or another source for prevent UI frozen. But when you try to change UI element from not UI thread it may throw some exception. To change UI from not UI thread you should add update task to UI thread:

您应该使用另一个(不是 UI)线程从 DB 或其他来源检索长数据,以防止 UI 冻结。但是当您尝试从非 UI 线程更改 UI 元素时,它可能会引发一些异常。要从非 UI 线程更改 UI,您应该将更新任务添加到 UI 线程:

Deployment.Current.Dispatcher.BeginInvoke(() => 
{
   some udate action here
});  

回答by avo

I believe you should treat your ViewModel in the same way you treat the UI itself. That is, don't modify it directly from a non-UI thread. INotifyPropertyChangedwill not automatically do the magic of marshaling from the worker thread to the UI thread, where controls can be updated.

我相信你应该像对待 UI 本身一样对待你的 ViewModel。也就是说,不要直接从非 UI 线程修改它。INotifyPropertyChanged不会自动执行从工作线程到 UI 线程的封送处理,在 UI 线程中可以更新控件。

Related questions:

相关问题:

INotifyPropertyChanged with threads

INotifyPropertyChanged 与线程

INotifyPropertyChanged causes cross-thread error

INotifyPropertyChanged 导致跨线程错误

回答by Med.Amine.Touil

add an event to your class as a property and use the background thread (dispatcher) to do your treatment. Once it ends,you invoke the event to upDate the window (the UI thread).

将事件作为属性添加到您的类中,并使用后台线程(调度程序)进行处理。一旦它结束,您就调用该事件来更新窗口(UI 线程)。

You can also use the background workerTheir are small threads used to execute code in a small threads and updates UI after finishing .

也可以使用后台worker它们的小线程,用于在小线程中执行代码,完成后更新UI。