Почему классы, такие как BindingList или ObservableCollection, не безопасны для потоков? - PullRequest
24 голосов
/ 09 февраля 2009

Снова и снова мне приходится писать поточно-ориентированные версии BindingList и ObservableCollection, потому что при привязке к пользовательскому интерфейсу эти элементы управления не могут быть изменены из нескольких потоков. Я пытаюсь понять, что это , почему это так - это ошибка проектирования или это намеренное поведение?

Ответы [ 4 ]

31 голосов
/ 09 февраля 2009

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

if ( myCollection.Count > 0 ) {
  var x = myCollection[0];
}

Предположим, что myCollection является потокобезопасной коллекцией, в которой добавления и обновления гарантированно не повреждают состояние. Этот код не является потокобезопасным и является условием гонки.

Почему? Несмотря на то, что myCollection безопасен, нет никакой гарантии, что не произойдут изменения между двумя вызовами метода myCollection: namedly Count и indexer. Другой поток может войти и удалить все элементы между этими вызовами.

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

РЕДАКТИРОВАТЬ

Я расширил это обсуждение в недавнем сообщении в блоге: http://blogs.msdn.com/jaredpar/archive/2009/02/11/why-are-thread-safe-collections-so-hard.aspx

6 голосов
/ 09 февраля 2009

Чтобы добавить немного к отличному ответу Джареда: безопасность потоков не приходит бесплатно. Многие (большинство?) Коллекций используются только в одном потоке. Почему эти коллекции должны иметь ограничения по производительности или функциональности, чтобы справиться с многопоточным корпусом?

5 голосов
/ 15 ноября 2012

Собирая идеи из всех других ответов, я думаю, что это самый простой способ решить ваши проблемы:

Измените свой вопрос с:

"Почему класс Х не вменяемый?"

до

«Какой разумный способ сделать это с классом X?»

  1. в конструкторе вашего класса, получите текущий диспетчер при создании ваши наблюдаемые коллекции. Поскольку, как вы указали, модификацию надо быть сделано в оригинальном потоке, который не может быть основным GUI-потоком. Так что App.Current.Dispatcher не всегда прав, и не у всех классов есть this. Диспетчер .

    _dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher;
    _data = new ObservableCollection<MyDataItemClass>();
    
  2. Используйте диспетчер для вызова разделов кода что нужно в оригинальной теме.

    _dispatcher.Invoke(new Action(() => { _data.Add(dataItem); }));
    

Это должно помочь тебе. Хотя в некоторых ситуациях вы можете предпочесть .BeginInvoke вместо .Invoke .

2 голосов
/ 09 февраля 2009

Если вы хотите сойти с ума - вот a ThreadedBindingList<T>, который автоматически отправляет уведомления обратно в поток пользовательского интерфейса. Однако было бы безопасно, чтобы только один поток делал обновления и т. Д. Одновременно.

...