У меня есть ObservableCollection<A> a_collection;
коллекция содержит n элементов. Каждый элемент A выглядит так:
public class A : INotifyPropertyChanged
{
public ObservableCollection<B> b_subcollection;
Thread m_worker;
}
По сути, все это связано со списком WPF + b_subcollection
элементом управления подробным представлением, который показывает выбранный элемент в отдельном списке (двусторонние привязки, обновления при изменении свойств и т. Д.).
Проблема обнаружилась у меня, когда я начал реализовывать потоки. Вся идея заключалась в том, чтобы весь a_collection
рабочий поток использовался для «работы», а затем обновлял их b_subcollections
и отображал результаты в реальном времени.
Когда я попробовал это, у меня было исключение, в котором говорилось, что только поток Dispatcher может изменять ObservableCollection, и работа остановилась.
Может ли кто-нибудь объяснить проблему и как ее обойти?
Ответы:
Технически проблема не в том, что вы обновляете ObservableCollection из фонового потока. Проблема в том, что когда вы это делаете, коллекция вызывает событие CollectionChanged в том же потоке, который вызвал изменение, что означает, что элементы управления обновляются из фонового потока.
Чтобы заполнить коллекцию из фонового потока, пока к нему привязаны элементы управления, вам, вероятно, придется создать свой собственный тип коллекции с нуля, чтобы решить эту проблему. Однако есть более простой вариант, который может сработать для вас.
Разместите вызовы Add в потоке пользовательского интерфейса.
public static void AddOnUI<T>(this ICollection<T> collection, T item) { Action<T> addMethod = collection.Add; Application.Current.Dispatcher.BeginInvoke( addMethod, item ); } ... b_subcollection.AddOnUI(new B());
Этот метод вернется немедленно (до того, как элемент будет фактически добавлен в коллекцию), а затем в потоке пользовательского интерфейса элемент будет добавлен в коллекцию, и все будут счастливы.
Реальность, однако, такова, что это решение, скорее всего, зависнет при большой нагрузке из-за всей межпоточной активности. Более эффективное решение могло бы объединить кучу элементов и периодически публиковать их в потоке пользовательского интерфейса, чтобы вы не вызывали потоки для каждого элемента.
Класс BackgroundWorker реализует шаблон, который позволяет вам сообщать о ходе выполнения через его метод ReportProgress во время фоновой операции. О ходе выполнения сообщается в потоке пользовательского интерфейса через событие ProgressChanged. Это может быть еще один вариант для вас.
источник
Новая опция для .NET 4.5
Начиная с .NET 4.5 существует встроенный механизм для автоматической синхронизации доступа к сбору и отправки
CollectionChanged
событий в поток пользовательского интерфейса. Чтобы включить эту функцию, вам необходимо позвонить из потока пользовательского интерфейса .BindingOperations.EnableCollectionSynchronization
EnableCollectionSynchronization
делает две вещи:CollectionChanged
события в этом потоке.Очень важно, что это не заботится обо всем : чтобы обеспечить поточно-безопасный доступ к изначально небезопасной для потоков коллекции, вы должны сотрудничать с фреймворком, получая ту же блокировку от фоновых потоков, когда коллекция собирается быть изменена.
Следовательно, шаги, необходимые для правильной работы:
1. Решите, какой тип блокировки вы будете использовать.
Это определит, какую перегрузку
EnableCollectionSynchronization
следует использовать. В большинстве случаев достаточно простогоlock
оператора, поэтому эта перегрузка является стандартным выбором, но если вы используете какой-то необычный механизм синхронизации, есть также поддержка пользовательских блокировок .2. Создайте коллекцию и включите синхронизацию.
В зависимости от выбранного механизма блокировки вызовите соответствующую перегрузку в потоке пользовательского интерфейса . При использовании стандартного
lock
оператора вам необходимо предоставить объект блокировки в качестве аргумента. При использовании настраиваемой синхронизации вам необходимо предоставитьCollectionSynchronizationCallback
делегата и объект контекста (что может бытьnull
). При вызове этот делегат должен получить вашу настраиваемую блокировку, вызватьAction
переданную ему и снять блокировку перед возвратом.3. Сотрудничайте, заблокировав коллекцию перед ее изменением.
Вы также должны заблокировать коллекцию, используя тот же механизм, когда собираетесь изменить ее самостоятельно; сделайте это с
lock()
тем же объектом блокировки, переданнымEnableCollectionSynchronization
в простом сценарии, или с тем же настраиваемым механизмом синхронизации в настраиваемом сценарии.источник
BeginInvoke
для запуска метода, который будет выполнять все соответствующие изменения в потоке пользовательского интерфейса [BeginInvoke
в любой момент времени не более одного будет ожидающим выполнения.В .NET 4.0 вы можете использовать эти однострочники:
.Add
Application.Current.Dispatcher.BeginInvoke(new Action(() => this.MyObservableCollection.Add(myItem)));
.Remove
Application.Current.Dispatcher.BeginInvoke(new Func<bool>(() => this.MyObservableCollection.Remove(myItem)));
источник
Код синхронизации коллекции для потомков. При этом используется простой механизм блокировки для включения синхронизации коллекции. Обратите внимание, что вам нужно включить синхронизацию коллекции в потоке пользовательского интерфейса.
public class MainVm { private ObservableCollection<MiniVm> _collectionOfObjects; private readonly object _collectionOfObjectsSync = new object(); public MainVm() { _collectionOfObjects = new ObservableCollection<MiniVm>(); // Collection Sync should be enabled from the UI thread. Rest of the collection access can be done on any thread Application.Current.Dispatcher.BeginInvoke(new Action(() => { BindingOperations.EnableCollectionSynchronization(_collectionOfObjects, _collectionOfObjectsSync); })); } /// <summary> /// A different thread can access the collection through this method /// </summary> /// <param name="newMiniVm">The new mini vm to add to observable collection</param> private void AddMiniVm(MiniVm newMiniVm) { lock (_collectionOfObjectsSync) { _collectionOfObjects.Insert(0, newMiniVm); } } }
источник