C#,MVVM,任务和UI线程

时间:2022-11-10 20:56:34

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.

我们有点困惑,当我们在任务线程而不是UI线程上填充ObservableCollection时,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);
    }
});

8 个解决方案

#1


3  

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线程上运行continuation的原因可能有很多。 MVVM框架可能有所帮助,或者其它东西使它在UI线程上运行,或者你只是幸运。

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

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

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

#2


4  

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 logical UI) as though it had UI thread affinity (like the literal UI). All data binding updates should be done in the UI context.

我同意@avo的意思是你应该把你的ViewModel(你的逻辑用户界面)视为具有UI线程亲和力(如文字用户界面)。所有数据绑定更新都应在UI上下文中完成。

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

正如@Cameron所指出的,这最容易通过异步完成:

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

#3


2  

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.

P.S - “myTextBlock”是视图中的文本块。

#4


2  

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

#5


1  

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

#6


1  

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中更改日期,则应该没有问题。将在视图模型中使用INotifyPropertyChanged引发ui更新。

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.

我更喜欢写异步并等待而不是继续。对于大多数同事来说,它更具可读性。但这只是代码的延迟,所以对你的实现没有什么不同。

#7


0  

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. INotifyPropertyChanged will 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线程的编组魔术,其中控件可以更新。

Related questions:

INotifyPropertyChanged with threads

带线程的INotifyPropertyChanged

INotifyPropertyChanged causes cross-thread error

INotifyPropertyChanged导致跨线程错误

#8


0  

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).

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

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

您还可以使用后台工作程序它们是用于在小线程中执行代码的小线程,并在完成后更新UI。

#1


3  

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线程上运行continuation的原因可能有很多。 MVVM框架可能有所帮助,或者其它东西使它在UI线程上运行,或者你只是幸运。

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

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

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

#2


4  

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 logical UI) as though it had UI thread affinity (like the literal UI). All data binding updates should be done in the UI context.

我同意@avo的意思是你应该把你的ViewModel(你的逻辑用户界面)视为具有UI线程亲和力(如文字用户界面)。所有数据绑定更新都应在UI上下文中完成。

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

正如@Cameron所指出的,这最容易通过异步完成:

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

#3


2  

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.

P.S - “myTextBlock”是视图中的文本块。

#4


2  

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

#5


1  

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

#6


1  

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中更改日期,则应该没有问题。将在视图模型中使用INotifyPropertyChanged引发ui更新。

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.

我更喜欢写异步并等待而不是继续。对于大多数同事来说,它更具可读性。但这只是代码的延迟,所以对你的实现没有什么不同。

#7


0  

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. INotifyPropertyChanged will 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线程的编组魔术,其中控件可以更新。

Related questions:

INotifyPropertyChanged with threads

带线程的INotifyPropertyChanged

INotifyPropertyChanged causes cross-thread error

INotifyPropertyChanged导致跨线程错误

#8


0  

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).

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

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

您还可以使用后台工作程序它们是用于在小线程中执行代码的小线程,并在完成后更新UI。