Это сложная проблема, так как большинство «решений» приводит к большому количеству пользовательского кода и множеству вызовов на BeginInvoke()
или System.ComponentModel.BackgroundWorker
(который сам по себе является просто тонкой оболочкой над BeginInvoke
).
В прошлом я также обнаружил, что вскоре вы захотите отложить отправку событий INotifyPropertyChanged
до тех пор, пока данные не станут стабильными. Код, который обрабатывает одно событие, измененное для правильности, часто должен читать другие свойства. У вас также часто есть элемент управления, который необходимо перерисовывать каждый раз, когда изменяется состояние одного из многих свойств, и вы не хотите, чтобы элемент управления перерисовывал себя слишком часто.
Во-первых, каждый пользовательский элемент управления WinForms должен читать все данные, которые ему нужны для рисования, в обработчике событий PropertyChanged
, поэтому ему не нужно блокировать объекты данных, когда это было сообщение WM_PAINT
(OnPaint
). Элемент управления не должен немедленно перерисовываться при получении новых данных; вместо этого он должен вызвать Control.Invalidate()
. Windows объединит сообщения WM_PAINT
в как можно меньшее количество запросов и отправляет их только тогда, когда потоку пользовательского интерфейса больше нечего делать. Это минимизирует количество перерисовок и время блокировки объектов данных. (Стандартные элементы управления в большинстве случаев делают это с привязкой данных)
Объекты данных должны записывать то, что изменилось по мере внесения изменений, а затем, как только набор изменений будет завершен, «пихнуть» поток пользовательского интерфейса в вызов метода SendChangeEvents
, который затем вызывает обработчик события PropertyChanged
(в потоке пользовательского интерфейса) для всех свойств, которые изменились. Во время работы метода SendChangeEvents()
объекты данных должны быть заблокированы, чтобы фоновые потоки не обновляли их.
Поток пользовательского интерфейса может быть «запущен» вызовом BeginInvoke
всякий раз, когда набор обновлений читает бин из базы данных. Часто лучше проводить опрос потока пользовательского интерфейса с использованием таймера, поскольку Windows отправляет сообщение WM_TIMER
только тогда, когда очередь сообщений пользовательского интерфейса пуста, что приводит к тому, что пользовательский интерфейс чувствует себя более отзывчивым.
Также подумайте о том, чтобы вообще не использовать привязку данных, и чтобы пользовательский интерфейс запрашивал каждый объект данных «что изменилось» при каждом срабатывании таймера. Привязка данных всегда выглядит красиво, но может быстро стать частью проблемы, а не частью решения.
Поскольку блокировка / разблокировка объектов данных является проблемой и может не позволить считываниям обновлений из базы данных достаточно быстро, вы можете передать потоку пользовательского интерфейса (виртуальную) копию объектов данных. Наличие объекта данных должно быть постоянным / неизменным, чтобы любые изменения в объекте данных возвращали новый объект данных, а не изменяли текущий объект данных, что может сделать это возможным.
Постоянные объекты звучат очень медленно, но не обязательно, см. это и , для некоторых указателей. Также посмотрите на это и , что на переполнении стека.
Также взгляните на retlang - Параллелизм на основе сообщений в .NET . Его пакетирование сообщений может быть полезным.
(Для WPF у меня была бы View-Model, устанавливаемая в потоке пользовательского интерфейса, который затем обновлялся в «пакетах» из многопоточной модели фоновым потоком. Однако WPF намного лучше сочетал привязку данных события то WinForms.)