2013-11-11 6 views
3

я выборки данных для окна МОФОВ в backgroundthread, как это [Framework 4.0 с асинхронным/ждать]:Обновление DataContext от Справочной нити

async void refresh() 
{ 
    // returns object of type Instances 
    DataContext = await Task.Factory.StartNew(() => serviceagent.GetInstances()); 
    var instances = DataContext as Instances; 
    await Task.Factory.StartNew(() => serviceagent.GetGroups(instances)); 
    // * problem here * instances.Groups is filled but UI not updated 
} 

Когда я включаю действие GetGroups в GetInstances интерфейс показывает группу ,
Когда я обновляюсь в отдельном действии, DataContext включает в себя группы correclty, но пользовательский интерфейс не отображает их.

В методе GetGroups() я включил NotifyCollectionChangedAction.Reset для групп ObservableCollection, и это не поможет.
Необычным является то, что я звоню NotifyCollectionChangedAction.Reset в список только один раз, но выполняется три раза, а список состоит из десяти элементов ?!

я могу решить эту проблему, написав:

DataContext = await Task.Factory.StartNew(() => serviceagent.GetGroups(instances)); 

Но это очередной способ для обновления DataContxt и пользовательский интерфейс с помощью процесса Backgound?
На самом деле, я хочу обновить существующий DataContext, не устанавливая его снова?

EDIT: serviceagent.GetGroups(instances) более подробно:

public void GetGroups(Instances instances) 
{ 
    // web call 
    instances.Admin = service.GetAdmin(); 

    // set groups for binding in UI 
    instances.Groups = new ViewModelCollection<Groep>(instances.Admin.Groups); 

    // this code has no effect 
    instances.Groups.RaiseCollectionChanged(); 
} 

Здесь ViewModelCollection<T> наследуется от ObservableCollection<T> и я добавил метод:

public void RaiseCollectionChanged() 
{ 
    var handler = CollectionChanged; 
    if (handler != null) 
    { 
     Trace.WriteLine("collection changed"); 
     var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); 
     handler(this, e); 
    } 
} 
+0

Если привязка в файле XAML выполнена правильно, я думаю, что проблема заключается в методе GetGroups. Можете ли вы показать нам этот метод? –

+0

В основном код является просто '{instance.Groups = new ViewModelCollection (webcall); RaiseCollectionChanged(); } ' – Gerard

+0

Пожалуйста, введите код в вопрос, а не комментарии. Также добавьте любой недостающий соответствующий код. Где вы вызываете Сброс и что он имеет отношение к остальной части вопроса? –

ответ

4

Там есть несколько моментов, которые выделяются в async части кода:

  • Я объясняю why we should avoid async void в моей статье MSDN. Таким образом, void - это неестественный тип возврата для методов async, поэтому он имеет некоторые особенности, особенно в отношении обработки исключений.
  • We should prefer TaskEx.Run over StartNew for asynchronous tasks, как я расскажу в своем блоге.
  • В то время как не точно требуется, рекомендуется следовать рекомендациям в Task-based Asynchronous Pattern; следующие соглашения об именах (и т. д.) помогут другим разработчикам поддерживать код.

Исходя из этого, я также рекомендую разместить в своем блоге сообщение intro to async.

На актуальной проблеме ...

Обновление кода с привязкой к данным из фоновых потоков всегда сложно. Я рекомендую вам обрабатывать данные ViewModel, как если бы они были частью пользовательского интерфейса (это, так сказать, «логический интерфейс»). Таким образом, для получения данных в фоновом потоке хорошо, но обновление фактических значений VM должно выполняться в потоке пользовательского интерфейса.

Эти изменения делают ваш код выглядеть следующим образом:

async Task RefreshAsync() 
{ 
    var instances = await TaskEx.Run(() => serviceagent.GetInstances()); 
    DataContext = instances; 
    var groupResults = await TaskEx.Run(() => serviceagent.GetGroups(instances)); 
    instances.Admin = groupResults.Admin; 
    instances.Groups = new ObservableCollection<Group>(groupResults.Groups); 
} 

public GroupsResult GetGroups(Instances instances) 
{ 
    return new GroupsResult 
    { 
    Admin = service.GetAdmin(), 
    Groups = Admin.Groups.ToArray(), 
    }; 
} 

Следующая вещь, которую вы должны проверить, является ли InstancesINotifyPropertyChanged орудия. При настройке Groups вам не нужно поднимать сборку Reset; так как Groups - это имущество на Instances, это - ответственность Instances поднять INotifyPropertyChanged.PropertyChanged.

В качестве альтернативы, вы можете просто установить DataContext последнее:

async Task RefreshAsync() 
{ 
    var instances = await TaskEx.Run(() => serviceagent.GetInstances()); 
    var groupResults = await TaskEx.Run(() => serviceagent.GetGroups(instances)); 
    instances.Admin = groupResults.Admin; 
    instances.Groups = new ObservableCollection<Group>(groupResults.Admin.Groups); 
    DataContext = instances; 
} 
+0

Спасибо за отличный совет w.r.t. 'TaskEx.Run', я понятия не имел. Вы также видели недостаток в моем «PropertyChanged». Один из недостатков вашего решения состоит в том, что для этого потребуется связать все контракты данных в проекте пользовательского интерфейса. Я хочу эти ссылки только в моем проекте ViewModel. – Gerard

+0

Итак, вместо 'instance.Groups = new ObservableCollection (groupResults.Groups);' в проекте UI я делаю то же самое в проекте VM, за которым следует 'RaisePropertyChanged (« Groups »);'. Разве это не так? MVVM вместо установки списков в представлении? – Gerard

4

Кажется, есть немного путаницы о том, что DataContext есть. DataContext - это не специальный объект, который необходимо обновить. Это ссылка на объект или объекты, которые вы хотите связать с вашим пользовательским интерфейсом. Всякий раз, когда вы вносите изменения в эти объекты, пользовательский интерфейс получает уведомление (если вы реализуете правильные интерфейсы).

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

Фактически, в вашем коде нет причин дважды устанавливать DataContext. Просто установите его с окончательным набором объектов, которые вы хотите отобразить. На самом деле, так как вы работаете на тех же данных, нет никаких причин, чтобы использовать две задачи:

async Task refresh() 
{ 
    // returns object of type Instances 
    DataContext=await Task.Factory.StartNew(() => { 
      var instances = serviceagent.GetInstances(); 
      return serviceagent.GetGroups(instances); 
    }); 
} 

ПРИМЕЧАНИЕ:

Вы должны нер использовать async void подпись. Он используется только для обработчиков событий «огонь-и-забыть», где вам все равно, удастся ли им или не удастся. Причина в том, что метод async void не может быть ожидаемым, поэтому никто не может узнать, удалось ли это или нет.

+0

async void работает, зачем мне нужна задача async? Обратите внимание, что GetInstances() не является методом void, только GetGroups() недействителен, я думал, что могу передать DataContext так, чтобы говорить по ссылке? – Gerard

1

я обнаружил, что RaiseCollectionChanged не имеет никакого влияния на свойства Groups где DataContext привязан. Я просто должен уведомить: instances.RaisePropertyChanged("Groups");.

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