Как правильно сообщить о прогрессе представлению WPF из задачи TPL? - PullRequest
3 голосов
/ 13 июля 2011

Я пытаюсь прочитать большое количество строк из базы данных небольшими «кусочками» или страницами и сообщить о ходе работы пользователю;т. е. если я загружаю 100 «чанков», то отчет о ходе выполнения каждого чанка загружается.

Я использую TPL в C # 4.0 для чтения этих чанков из базы данных, а затем передаю полный набор результатов другомуЗадача, которая может использовать его.Я чувствую, что TPL дает мне лучший контроль над отменой и передачей задач, чем BackgroundWorker и т. Д., Но, похоже, нет встроенного способа сообщить о ходе выполнения задачи.

Вотрешение, которое я реализовал, чтобы сообщать о прогрессе в индикатор выполнения WPF, и я хочу убедиться, что это уместно и что нет лучшего подхода, который я должен использовать.

Я начал с создания простого интерфейса для представленияизменение прогресса:

public interface INotifyProgressChanged
{
  int Maximum { get; set; }
  int Progress { get; set; }
  bool IsIndeterminate { get; set; }
}

Эти свойства могут быть связаны с ProgressBar в представлении WPF, а интерфейс реализуется вспомогательной моделью представления, которая отвечает за инициирование загрузки данных и в конечном итоге сообщает об общем ходе (для этого примера):

public class ContactsViewModel : INotifyProgressChanged
{
  private IContactRepository repository;

  ...

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

Вы заметите, что я передаю ViewModel методу репозитория как INotifyProgressChanged, и именно здесь я хочу убедиться, что я не делаю что-то не так.

Мой мыслительный процесс заключается в том, чтобы сообщать о прогрессеs, метод, фактически выполняющий работу (метод репозитория), нуждается в доступе к интерфейсу 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();
  }
}

И именно так репозиторий в конечном итоге сообщает о прогрессе в View через ViewModel.Это правильный подход?Правильно ли я использую TPL, нарушаю ли основные правила и т. Д.?Это решение работает, о прогрессе сообщается, как и ожидалось, я просто хочу убедиться, что я не настраиваю себя на неудачу.

Ответы [ 3 ]

6 голосов
/ 14 июля 2011

«Предписанный» способ сделать это - передать экземпляр TaskScheduler из TaskSheduler::FromCurrentSynchronizationContext в ContinueWith, который вы хотите обеспечить в ветке диспетчера WPF.

Например:

 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 голосов
/ 13 июля 2011

Я рекомендую вам не обновлять привязанные к данным свойства из фоновых потоков.

Чтобы решить эту проблему, вы можете попросить фоновую задачу опубликовать задачу пользовательского интерфейса для ее обновления или (еще лучше), используйте систему IProgress<T> / Progress<T>, описанную в документе Обзор асинхронных шаблонов на основе задач .

Подход IProgress<T> хорош, поскольку он разделяет вашу фоновую задачуиз обновлений ViewModel.Однако у него есть некоторые недостатки (обмен данными между фоновой задачей и обновлением и обработка исключений из обновления);Я надеюсь, что они адресованы до официального релиза Async CTP.

1 голос
/ 14 июля 2011

Я не думаю, что вы должны напрямую обновлять ViewModel напрямую из фонового потока.Я пишу много приложений для Silverlight, и мне нравится использовать инструментарий MVVMLight для реализации шаблона MVVM.

В MVVM иногда требуется, чтобы ViewModel «воздействовал» на View, что вы не можете сделать напрямую, потому что ViewModel не имеет ссылки на View.В этих сценариях MVVMLight имеет класс Messenger, который позволяет нам «прослушивать» сообщения в представлении и «уведомлять» из ViewModel.

Я считаю, что вы должны использовать класс Messenger в вашем сценарии.

Вот ссылка с примером кода: http://chriskoenig.net/2010/07/05/mvvm-light-messaging/

...