2013-12-24 3 views
1

Я создаю ObservableCollection объектов ViewModel, где каждый объект имеет несколько задач для завершения, когда он инициализируется.Асинхронно обновлять несколько свойств в списке

Я добавляю их к ObservableCollection, как это в родительском ViewModel:

public async void ButtonPressCommandHandler() 
{ 
    for (int i = 0; i < 25; ++i) 
    { 
     var testViewModel = new TestViewModel(); 
     await testViewModel.Initialize(); 
     TestViewModels.Add(testViewModel); 
    } 
} 

Этот цикл просто вызывается на кнопку мыши или другого события.

А вот код тест ViewModel:

public class TestViewModel : ViewModelBase 
{ 
    private string _taskOne; 
    public string TaskOne 
    { 
     [DebuggerStepThrough] 
     get { return _taskOne; } 
     set 
     { 
      if (value != _taskOne) 
      { 
       _taskOne = value; 
       RaisePropertyChanged(() => TaskOne); 
      } 
     } 
    } 

    private string _taskTwo; 
    public string TaskTwo 
    { 
     [DebuggerStepThrough] 
     get { return _taskTwo; } 
     set 
     { 
      if (value != _taskTwo) 
      { 
       _taskTwo = value; 
       RaisePropertyChanged(() => TaskTwo); 
      } 
     } 
    } 

    public async Task Initialize() 
    { 
     TaskOne = await TaskOneAsync(); 
     TaskTwo = await TaskTwoAsync(); 
    } 

    private Task<string> TaskOneAsync() 
    { 
     return Task.Factory.StartNew(() => 
     { 
      Thread.Sleep(100); 
      return "Task one"; 
     }); 
    } 

    private Task<string> TaskTwoAsync() 
    { 
     return Task.Factory.StartNew(() => 
     { 
     var random = new Random(); 
     int randomNumber = random.Next(500, 5000); 
     Thread.Sleep(randomNumber); 
     return "Task two: " + randomNumber; 
      Thread.Sleep(randomNumber); 
      return "Task two: " + randomNumber; 
     }); 
    } 
} 

(. Я знаю, что могу назвать инициализации работы в конструкторе, но это ближе к тому, что мне действительно нужно сделать)

В моей view, у меня есть ListView, где ListView.ItemTemplate просто отображает текстовые блоки с объектами TaskOne и TaskTwo. Это ItemSource обязателен для TestViewModels.

Что я вижу, так это то, что для каждого из 25 объектов создаются объекты TaskOne и TaskTwo одновременно, и каждый объект отображается только после завершения обеих задач.

Если удалить await из Initialize() и имеет Initialize() возвращение пустоты (это в потоке пользовательского интерфейса) поведение лучше- я вижу все TaskOne свойств очень быстро, а затем TaskTwo свойства начинают заполнять. Но случайное значения, которые они отображают, неправильны - существует много дубликатов, и они, похоже, заполняют список кусками или 4 или 5 (что трудно сказать).

Вся цель этого в не-тестовом коде заключается в обновлении индикатора прогресса, привязанного к TaskTwo. Что-то вроде этого (от TestViewModel кода):

public async Task Initialize() 
{ 
    TaskOne = await TaskOneAsync(); 
    Loading = true; 
    TaskTwo = await TaskTwoAsync(); 
    Loading = false; 
} 

Где все объекты загрузки сразу в список, а затем асинхронно обновить свои TaskOne и TaskTwo свойства, как они завершат, и обновлять индикатор прогресса на основе от работы в TaskTwo. Но пока не удастся заставить это работать.

EDIT: Добавлен лучший пример кода и объяснения

+0

1. Ваш ViewModel реализует 'INotifyPropertyChanged'? 2. Вам известно, что вы запускаете задачу «async» и не ожидаете приведенной задачи? – i3arnon

+0

2 *. На первый взгляд я вижу, что вы используете 'async void', который настоятельно рекомендуется против (если это не обработчик событий UI) – i3arnon

+0

Не могли бы вы опубликовать короткий, но * полный * код, который демонстрирует проблему? Трудно понять, что происходит из этих нескольких фрагментов. – svick

ответ

0

Во-первых, то, как код написан результат обязательно должен быть, как вы описали.

Что я вижу, так это то, что для каждого из 25 объектов создаются объекты TaskOne и TaskTwo одновременно, а объект s sadfach отображается только после завершения обеих задач.

Линия await testViewModel.Initialize(); ожидает Initialize метод, который по очереди awaits две задачи. Это означает, что вы добавляете элемент (TestViewModels.Add(testViewModel);) только после того, как обе задачи уже завершены.

Во-вторых, если вы не сделаете awaitInitialize, вы можете позвонить ему еще до завершения предыдущего запуска, что означает, что вы запускаете 2 задания параллельно (используя StartNew). Это приведет к тому, что несколько случайных экземпляров будут созданы довольно близко друг к другу.Класс Random использует семантику, основанную на времени, для генерации значений, и в этом случае семя будет таким же, следовательно, такими же «случайными» результатами. Из статьи случайного MSDN:

Начальное значение по умолчанию получено из системных часов и имеет конечное разрешение. В результате различные объекты Random, которые создаются с тесной последовательностью при вызове конструктора по умолчанию, будут иметь одинаковые начальные значения по умолчанию и, следовательно, будут создавать одинаковые наборы случайных чисел. Эту проблему можно избежать, используя один случайный объект для генерации всех случайных чисел. Вы также можете работать вокруг него, изменяя начальное значение, возвращенное системными часами, а затем явно обеспечивая это новое начальное значение в случайном порядке (Int32) конструктор

Наконец, я хотел бы обескуражить, используя как Task.Factory.StartNew и Thread.Sleep в async-await Окружающая среда. Вероятно, вы должны использовать Task.Run и await Task.Delay (я понимаю, что это пример кода).

+1

Интересно ... это имеет смысл - я подозревал, что было какое-то странное со случайным поколением, но не было уверенности, что это была отдельная проблема ... ура! – Nicros

Смежные вопросы