2014-01-30 3 views
5

У нас есть приложение, построенное в соответствии с шаблоном MVVM. В разное время мы запускаем задачи для перехода к базе данных для извлечения данных, затем мы заполняем ObservableCollection, с которым связан элемент управления WPF с этими данными.C#, MVVM, задачи и пользовательский интерфейс

Мы немного смущены, когда мы заполняем ObservableCollection, мы делаем это в потоке задачи, а не в потоке пользовательского интерфейса, но пользовательский интерфейс все еще обновляется/ведет себя правильно. Мы ожидали ошибки и должны были изменить код, чтобы заполнить коллекцию в потоке пользовательского интерфейса.

Является ли это опасным сценарием и все равно нужно заполнять нить пользовательского интерфейса?

код для получения данных:

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

Если извлечение данных из БД занимает много времени, чем вы не должны делать это в потоке пользовательского интерфейса, так как оно будет блокировать или замораживать экран, который по сути не является хорошим пользователем -experience. Вы должны выполнить эту задачу в другом потоке. – Vishal

+0

Простое решение. Проверьте поток, в котором вы находитесь. Нет никакой необходимости предполагать что-либо. – Will

ответ

3

Может существовать множество причин, по которым продолжение продолжается в потоке пользовательского интерфейса. Рамка MVVM может помочь, или что-то еще делает ее запущенной в потоке пользовательского интерфейса, или вам просто повезло.

Для обеспечения продолжения работы в потоке пользовательского интерфейса вы можете захватить 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. 

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

Если вы можете использовать async/await, тогда код станет намного проще.

var result = await Task.Factory.StartNew(
    () => UiDataProvider.RefreshForwardContractReport(fcrIdField.Value), 
    TaskCreationOptions.LongRunning 
); 

if (result.OperationSuccess) 
{ 
    RefreshReport(result.OperationResult); 
} 
1

Вы должны использовать другой (не UI) поток для получения длинных данных из БД или другой источник предотвратить UI замерзшей. Но когда вы пытаетесь изменить элемент пользовательского интерфейса из не потока пользовательского интерфейса, это может вызвать какое-то исключение. Чтобы изменить пользовательский интерфейс с не UI нити следует добавить задачу обновления для UI нити:

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

Учитывая сценарий MVVM, когда вы находитесь в части ContinueWith, я согласен, что вы находитесь в потоке Non-UI и вы обновление свойств в ViewModel (которые привязаны к элементам UI), а не сами элементы пользовательского интерфейса.

Попробуйте обновить элементы пользовательского интерфейса в части ContinueWith, скажем, как

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

В этом сценарии u'll получить исключение, которое вы ожидаете.

P.S - "myTextBlock" - текстовый блок в вашем представлении.

1

Если вы изменили свою дату только в ViewModel, проблем не должно быть. Обновления ui будут подняты с использованием INotifyPropertyChanged в вашей модели просмотра.

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

0

Я считаю, что вы должны относиться к своей ViewModel так же, как и к самому пользовательскому интерфейсу. То есть, не изменяйте его непосредственно из потока, отличного от UI. INotifyPropertyChanged не будет автоматически делать магию маршалинга из рабочего потока в поток пользовательского интерфейса, где элементы управления могут обновляться.

Похожие вопросы:

INotifyPropertyChanged with threads

INotifyPropertyChanged causes cross-thread error

+2

«INotifyPropertyChanged не будет автоматически делать магию маршалинга из рабочего потока в поток пользовательского интерфейса». Фактически, привязка, которая следит за свойством INPC (т. Е. Не DependencyProperty) ***, *** будет маршировать все обновления в пользовательский интерфейс нить. Полагаю, это было добавлено в 4.0. Попробуй сам. – Will

0

добавить событие в свой класс как свойство и использовать фоновый поток (диспетчерская), чтобы сделать ваше лечение. Как только он закончится, вы вызываете событие, чтобы обновить окно (поток пользовательского интерфейса).

Вы также можете использовать background worker. Они представляют собой небольшие потоки, используемые для выполнения кода в небольших потоках и обновления интерфейса после завершения.

4

WPF позволяет разрешать больше операций с поперечными потоками с каждым выпуском. Другие платформы MVVM не позволяют их вообще. В случае ObservableCollection, если вы не используете EnableCollectionSynchronization, обновление с фоновым потоком неверно.

Я согласен с @avo в том, что вы должны относиться к вашей ViewModel (ваш логического UI), как если бы он имел UI нить сродство (подобно буквальный UI). Все обновления привязки данных должны выполняться в контексте пользовательского интерфейса.

Как @Cameron отметил, это легко делается с помощью async:

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

Это происходит потому, что WPF автоматически отправляет событие PropertyChanged к основному потоку, в отличие от всех других структур XAML. Во всех других рамках требуется диспетчерское решение. Лучшая статья, которую я прочитал о MVVM и многопоточности https://msdn.microsoft.com/en-us/magazine/dn630646.aspx

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