【问题标题】:C#, MVVM, Tasks and the UI ThreadC#、MVVM、任务和 UI 线程
【发布时间】:2014-01-30 10:14:01
【问题描述】:

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

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

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

获取数据的代码:

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

【问题讨论】:

  • 如果从数据库中检索数据花费的时间比你不应该在 UI 线程上这样做,因为它会拖拽或冻结屏幕,这通常不是一个好的用户体验。你应该在不同的线程。
  • 简单的解决方案。检查你所在的线程。没有必要假设任何事情。

标签: c# wpf multithreading mvvm task


【解决方案1】:

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

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

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

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

【讨论】:

    【解决方案2】:

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

    为确保继续在 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.
    

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

    如果您可以使用 async/await,那么代码会变得更加简单。

    var result = await Task.Factory.StartNew(
        () => UiDataProvider.RefreshForwardContractReport(fcrIdField.Value),
        TaskCreationOptions.LongRunning
    );
    
    if (result.OperationSuccess)
    {
        RefreshReport(result.OperationResult);
    }
    

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

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

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

        在那种情况下,你会得到你所期望的异常。

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

        【讨论】:

          【解决方案5】:

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

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

          【讨论】:

            【解决方案6】:

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

            我更喜欢写 async 和 await 而不是 continue with。对于大多数同事来说,它更具可读性。但这只是代码问题,所以你的实现不会有什么不同。

            【讨论】:

              【解决方案7】:

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

              相关问题:

              INotifyPropertyChanged with threads

              INotifyPropertyChanged causes cross-thread error

              【讨论】:

              • “INotifyPropertyChanged 不会自动执行从工作线程到 UI 线程的封送”实际上,正在监视 INPC 属性的绑定(即,不是 DependencyProperty) 将所有更新编组到 UI 线程。我相信这是在 4.0 中添加的。自己试试吧。
              【解决方案8】:

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

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-07-07
                • 1970-01-01
                • 2013-03-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多