2010-01-20 5 views
12

В приложении WPF ObservableCollection заполняется и обновляется с помощью запросов LINQ to SQL. Затем объекты UI обновляются с использованием значений из этого ObservableCollection.Обновление ObservableCollection в отдельном потоке

Возможно ли и разумно, что операции по обновлению этого ObservableCollection с помощью запросов LINQ to SQL выполнялись в отдельном потоке?

Если да, то в этом случае это будет один и тот же экземпляр этого ObservableCollection? (Я имею в виду, если это не то же самое для принятия значений из LINQ datacontext, а другое для предоставления значений для обновления пользовательского интерфейса, то я не смогу обновить интерфейс)

ответ

24

С помощью встроенного класса ObservableCollection<T> вы не можете изменять контент из отдельного потока, если пользовательский интерфейс привязан к коллекции, он выбрасывает NotSupportedException (но изменение уведомления о свойствах элементов коллекции отлично работает). Я написал AsyncObservableCollection<T> class для обработки этого случая. Он работает путем вызова обработчиков событий в контексте синхронизации UI

+0

Возможная альтернатива http://stackoverflow.com/questions/12881489/asynchronously-adding-to-observablecollection-or-an-alternative – Narkha

+0

Или вы можете попробовать это, которое прекрасно отвечает потоку, работает из любой темы и может быть databound несколькими потоками пользовательского интерфейса: http://www.codeproject.com/Articles/64936/Multithreaded-ObservableImmutableCollection – Anthony

+0

Хорошая работа в коллекции !! –

6

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

Охватывает. Я был неверно проинформирован =))

Правильно, мы на самом деле подклассифицируем ObservableCollection<T> и переопределяем метод OnCollectionChanged, чтобы исключить исключение перекрестной интерпретации пользовательского интерфейса. Мы используем this solution:

public class MTObservableCollection<T> : ObservableCollection<T> 
{ 
    public override event NotifyCollectionChangedEventHandler CollectionChanged; 
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 
    { 
     var eh = CollectionChanged; 
     if (eh != null) 
     { 
     Dispatcher dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList() 
       let dpo = nh.Target as DispatcherObject 
       where dpo != null 
       select dpo.Dispatcher).FirstOrDefault(); 

     if (dispatcher != null && dispatcher.CheckAccess() == false) 
     { 
      dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e))); 
     } 
     else 
     { 
      foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()) 
       nh.Invoke(this, e); 
     } 
    } 
    } 
} 

Без переопределёны вы получите исключение, как этого

System.NotSupportedException: Этот типа CollectionView не поддержки изменения в его SourceCollection из нить отличается от Диспетчерской нити.

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

+2

Как это возможно? Наверняка вы перенаправляетесь обратно в поток пользовательского интерфейса, чтобы применить изменения в хранилище данных? В противном случае вы получите исключение ... –

+3

Вам необходимо создать подкласс класса ObservableCollection , который отправляет событие CollectionChanged обратно в поток пользовательского интерфейса. Я считаю, что это проще, чем управление обновлением ViewModel. –

+0

Спасибо, Кент, Найджел - вы правы! Я исправил свой ответ, это была моя ошибка. –

2

Пытаясь понять ваш вопрос здесь:

 
Scenario 1 
1. LINQ to SQL retrieves data set from database and adds to ObservableCollection A. 
2. Periodically, more data is retrieved from database and added to A. Old data is removed from A. 

Scenario 2 
1. LINQ to SQL retrieves data set from database and adds to ObservableCollection A. 
2. Periodically, data in A is updated with new data from database (no add/remove). 

С Сценарием 1, вы будете иметь, чтобы использовать поток пользовательского интерфейса. В потоке пользовательского интерфейса принадлежит ObservableCollection, и вы получите исключение, если попытаетесь использовать его в другом потоке.

С Сценарием 2, большими пальцами. Пока вы не пытаетесь добавлять или удалять элементы из самой коллекции, вы можете обновлять элемент столько, сколько хотите в фоновом потоке.

15

.Net 4.5 обеспечивает решение в классе BindingOperations.

Теперь вы можете использовать BindingOperations.Метод EnableCollectionSynchronization следующим образом:

private readonly object _personCollectionLock; 
private ObservableCollection<Person> _personCollection; 

public ObservableCollection<Person> PersonCollection 
{ 
    get { return _personCollection; } 
    set 
    { 
    _personCollection = value; 
    BindingOperations.EnableCollectionSynchronization(_personCollection, _personCollectionLock); 
    } 

Я только попробовал это в моей среде разработки, но кажется, что все работает правильно теперь, когда я обновляю коллекцию из фонового потока.

Там будет обсуждение более углубленного этого решения на: http://10rem.net/blog/2012/01/16/wpf-45-observable-collection-cross-thread-change-notification

Запись MSDN для этого метода является то, по адресу: https://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.enablecollectionsynchronization(v=vs.110).aspx

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