2015-01-02 3 views
1

Я пишу приложение для WPF в шаблоне MVC. Цель приложения - отобразить некоторые данные в базе данных, и эти данные обновляются асинхронно.Threadsafe observer pattern

Я подумываю о том, как проектировать архитектуру, чтобы она была потокобезопасной. В частности:

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

Очевидно, что страница, которая только что закрывается, должна отказаться от подписки на услугу, а страница, которая только что появляется, должна (или может) подписаться.

я мог бы поставить подписку внутри критической секции, а также передачи о новых данных, но представьте себе следующий сценарий (стр ~ = его ViewModel, это не имеет большого значения, здесь):

  • служба входит критический раздел для передачи информации о новых данных (в отдельном потоке)
  • Страница пытается войти в критический раздел, чтобы отказаться от подписки (в основном потоке)
  • Служба сообщает страницу о новых данных (в отдельной теме).
  • Страница заполняет поля и вызывает событие PropertyChange (в отдельном потоке).
  • Событие PropertyChange сортируется по основному потоку. Который ждет критический раздел.

И это похоже на тупик для меня.

Как я могу безопасно спроектировать эту архитектуру, чтобы избежать таких взаимоблокировок? Возможно, страницы никогда не должны отписываться? Или есть другой способ защитить потоки, чтобы они не зашли в тупик?

+0

Это выглядит действительно странно, как подход. В частности, как «служба» знает, что некоторые данные являются новыми? это потому, что он только что был представлен вашим собственным приложением? – zaitsman

+1

Почему главный поток должен получить критический раздел? (в последней точке пули)? Также, почему вы стреляете в события, удерживая замки ?, это очень вероятно приведет к блокировкам! Также будет полезен некоторый код, демонстрирующий проблему. –

+0

Вообще говоря, вы не можете иметь тупик с ресурсом _one_. Чтобы иметь тупик, у вас должно быть 2 или более охраняемых ресурсов и закрытый цикл блокировки -> заблокированный график. Вы используете термин «критический раздел» (что не является проблемой), но вы должны очистить, какие функции охраны/мьютекса/семафора/etc вы используете, у вас есть 2 или более или только один. Я предполагаю, что после выполнения части кода отмены подписки (в основном потоке пользовательского интерфейса) маршаллированная (поставленная в очередь) PropertyChange будет работать без блокировки. Конечно, это может вызвать проблемы, если структуры данных недействительны, однако это не имеет ничего общего с тупиком. –

ответ

0

Учитывая, что сообщение помечено WPF и WP-8.1 и разъяснение в комментариях, я хотел бы сделать следующее:

  1. Есть базовый класс модели (один со свойствами, имеющих соответствующие данные) осуществлять INotifyPropertyChanged
  2. Имейте модель для ВСЕХ страниц как ObservableCollection<BaseModel>. Модель также должна реализовать свойство mutex/lock, созданное в конструкторе.
  3. Поделитесь моделью на всех моделях (например, поделитесь экземпляром модели).
  4. В выполнении операции async «Сервис» я бы только lock раздел кода, который бы Add или Remove элементов из модели ObservableCollection с использованием объекта блокировки самой модели. Этот раздел ДОЛЖЕН быть размещен в вызове Dispatcher.Invoke() или эквивалентной платформе. Это гарантирует, что только поток пользовательского интерфейса ожидает обновления коллекции.
  5. Я бы привязал весь пользовательский интерфейс на соответствующих страницах к ссылке на модель в модели viewmodel.

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

+0

(Не оценивая весь ответ, просто указывая на ошибку) Если операции добавления/удаления размещены в Dispatcher.Invoke(), тогда нет необходимости блокировать: все доступ к ObservableCollection, сериализованный в поток пользовательского интерфейса, следовательно, не вызывает проблем с параллелизмом и состояние гонки происходит на этом ресурсе. –

+0

@ g.pickardou Я считал это, и вы правы. Но это гарантирует, что никакой другой cpde не сможет изменить коллекцию (например, если это только часть приложения). И если это всего лишь один поток, блокировка, в то время как вводит в заблуждение, будет минимальной накладной. – zaitsman

+1

«это гарантирует, что никакой другой код не сможет изменить». Блокировка не гарантирует этого. Любой (неустойчивый) код может все еще изменять коллекцию, просто не используя тот же самый защитник или не сортируя операцию изменения в поток пользовательского интерфейса. Сама блокировка блокирует _nothing_. Как красный свет в движении, водители, которые его уважают, «синхронизированы», другие могут стать причиной аварии. Поэтому выполнение как блокировки, так и сериализации не требуется. –

0

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

В качестве альтернативы вы можете взглянуть на Rx (Reactive Extensions), что именно для этой цели: Реализация шаблона наблюдателя многопоточным способом.


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


«Или есть другой способ защитить потоки, чтобы они не зашли в тупик?» В настоящее время в среде .NET нет волшебного трюка, который автоматически предотвращает тупик. Другие многопоточные среды могут или не могут обеспечивать автоматический тупик разрешение (обратите внимание: не профилактическое) обслуживание, что может обнаружить тупик (после его возникновения) и автоматически выбрать жертву. В .NET это может быть исключение, что происходит пока вы ожидаете своего ресурса. (опять же это еще не реализовано)