从TPL任务向WPF视图报告进度的适当方法是什么?

3
我正在尝试从数据库中分批读取大量行,并向用户报告进度;即,如果我正在加载100个“块”,则在每个块加载时报告进度。
我正在使用C# 4.0中的TPL从数据库中读取这些块,然后将完整结果集交给另一个可以使用它的任务。我觉得TPL比BackgroundWorker等更好地控制任务取消和交接,但似乎没有内置的方法来报告任务的进度。
这是我实现的向WPF进度条报告进度的解决方案,我想确保这是适当的,并且是否应该采取更好的方法。
我首先创建了一个简单的接口来表示改变进度:
public interface INotifyProgressChanged
{
  int Maximum { get; set; }
  int Progress { get; set; }
  bool IsIndeterminate { get; set; }
}

这些属性可以绑定到WPF视图中的进度条,接口由支持ViewModel实现,负责启动数据加载并最终报告总体进度(此示例简化):

public class ContactsViewModel : INotifyProgressChanged
{
  private IContactRepository repository;

  ...

  private void LoadContacts()
  {
    Task.Factory.StartNew(() => this.contactRepository.LoadWithProgress(this))
      .ContinueWith(o => this.UseResult(o));
  }
}

您会注意到我将ViewModel作为INotifyProgressChanged传递给存储库方法,这就是我想确保自己没有做错事情的地方。

我的思路是,为了报告进度,实际执行工作的方法(即存储库方法)需要访问INotifyProgressChanged接口,以便最终更新视图来报告进度。以下是存储库方法的简短示例:

public class ContactRepository : IContactRepository
{
  ...

  public IEnumberable<Contact> LoadWithProgress(INotifyProgressChanged indicator)
  {
    var resultSet = new List<Contact>();
    var query = ... // This is a LINQ to Entities query

    // Set the maximum to the number of "pages" that will be iterated
    indicator.Maximum = this.GetNumberOfPages(query.Count(), this.pageSize);

    for (int i = 0; i < indicator.Maximum; i++)
    {
      resultSet.AddRange(query.Skip(i * this.pageSize).Take(this.pageSize));
      indicator.Progress += 1; // As each "chunk" is loaded, progress is updated
    }

    // The complete list is returned after all "chunks" are loaded
    return resultSet.AsReadOnly();
  }
}

这就是仓库通过ViewModel最终向View报告进度的方式。这种方法正确吗?我是否正确使用了TPL,是否违反了重要规则等?这个解决方案正在工作,进度如预期地报告,我只想确保自己不会失败。

3个回答

6

这样做的“规定”方式是将TaskScheduler实例从TaskSheduler::FromCurrentSynchronizationContext传递到您想要确保在WPF调度程序线程上执行的ContinueWith

例如:

 public void DoSomeLongRunningOperation()
 {
    // this is called from the WPF dispatcher thread

    Task.Factory.StartNew(() =>
    {
        // this will execute on a thread pool thread
    })
    .ContinueWith(t =>
    {
        // this will execute back on the WPF dispatcher thread
    },
    TaskScheduler.FromCurrentSynchronizationContext());
 }

5
我建议您避免从后台线程更新数据绑定属性。
为了解决这个问题,您可以让后台任务发布UI任务以进行更新,或者更好的方法是使用在基于任务的异步模式概述文档中描述的IProgress<T>/Progress<T>系统。 IProgress<T>方法很好,因为它将您的后台任务与ViewModel更新分开。然而,它也有一些缺点(共享后台任务和更新之间的数据;以及处理更新中的异常)。我希望这些问题在正式的Async CTP发布之前得到解决。

1

我认为你也不应该直接从后台线程更新ViewModel。我写了很多Silverlight应用程序,我喜欢使用MVVMLight工具包来实现MVVM模式。

在MVVM中,有时您需要使ViewModel“影响”View,但是由于ViewModel没有对View的引用,因此无法直接执行此操作。在这些情况下,MVVMLight具有Messenger类,允许我们在View中“监听”消息并从ViewModel中“通知”。

我认为您应该在您的场景中使用Messenger类。

这里是一个带有示例代码的链接:http://chriskoenig.net/2010/07/05/mvvm-light-messaging/


我赞同这个。你可以发送一个 NotificationMessage<ProgressClass> 消息到 UI 来更新。 - Rick Rat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接