WinForms многопоточный сценарий привязки данных, лучшая практика?

В настоящее время я разрабатываю/перерабатываю часть привязки данных приложения, которая сильно использует привязку и обновления winforms, исходящие из фонового потока (один раз в секунду нa > 100 записей).

Предположим, что приложение является торговым приложением, где фоновый поток отслеживает изменения данных и помещает их в объекты данных. Эти объекты хранятся в BindingList<> и реализуют INotifyPropertyChanged для распространения изменений посредством привязки данных к элементам winforms. Кроме того, объекты данных в настоящее время сортируют изменения через WinformsSynchronizationContext.Send в поток пользовательского интерфейса. Пользователь может ввести некоторые из значений в пользовательском интерфейсе, что означает, что некоторые значения могут быть изменены с обеих сторон. И значения пользователя не должны быть перезаписаны обновлениями.

Итак, у меня возникает вопрос:

  • Есть ли общий дизайн-гильдия, как это сделать (обновления фона в привязке данных)?
  • Когда и как маршалировать поток пользовательского интерфейса?
  • Каков наилучший способ взаимодействия фонового потока с объекты привязки/данных?
  • Какие классы/интерфейсы должны использоваться? (BindingSource,...)
  • ...

Пользовательский интерфейс действительно не знает, что существует фоновый поток, который обновляет элемент управления, и, насколько я понимаю, в сценариях привязки данных пользовательский интерфейс не должен знать, откуда поступают данные... Вы можете думать о фоновый поток как что-то, что подталкивает данные к пользовательскому интерфейсу, поэтому я не уверен, что фоновая работа - это вариант, который я ищу.

Иногда вы хотите получить некоторый ответ UI во время операции в объекте data/business (например, задание фона во время пересчетов). Повысить свойство, измененное на свойство состояния, связанное с фоном, недостаточно, так как элемент управления перерисовывается после завершения вычисления? Моя идея - захватить событие propertychanged и вызвать .update() в элементе управления... Любые другие идеи об этом?

Ответ 1

Это сложная проблема, поскольку большинство "решений" приводят к большому количеству настраиваемого кода и множеству вызовов BeginInvoke() или System.ComponentModel.BackgroundWorker (который сам по себе является лишь тонкой оболочкой поверх BeginInvoke).

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

Во-первых, каждый пользовательский элемент управления WinForms должен считывать все данные, необходимые ему для рисования в обработчике событий PropertyChanged, поэтому ему не нужно блокировать какие-либо объекты данных, когда это было сообщение WM_PAINT (OnPaint). Контроль не должен сразу перерисовываться, когда он получает новые данные; вместо этого он должен называть Control.Invalidate(). Windows объединяет сообщения WM_PAINT как можно меньше запросов и отправляет их только тогда, когда нить пользовательского интерфейса не имеет ничего общего. Это минимизирует количество перерисовок и время блокировки объектов данных. (Стандартные средства управления в основном делают это с привязкой данных в любом случае)

Объектам данных необходимо записать то, что было изменено по мере внесения изменений, после того как набор изменений был завершен, "ударьте" поток пользовательского интерфейса с вызовом метода SendChangeEvents, который затем вызывает обработчик события PropertyChanged (в потоке пользовательского интерфейса) для всех измененных свойств. Пока выполняется метод SendChangeEvents(), объекты данных должны быть заблокированы, чтобы остановить фоновый поток от их обновления.

Поток пользовательского интерфейса можно "пинать" вызовом BeginInvoke всякий раз, когда набор обновлений имеет bean, читаемый из базы данных. Часто лучше иметь опрос пользовательского интерфейса с помощью таймера, поскольку Windows отправляет сообщение WM_TIMER, когда очередь сообщений UI пуста, что приводит к более чувствительному интерфейсу пользователя.

Также рассмотрите возможность не использовать привязку данных вообще, и когда пользовательский интерфейс запрашивает каждый объект данных "что изменилось" каждый раз, когда срабатывает таймер. Связывание данных всегда выглядит хорошо, но может быстро стать частью проблемы, а не частью решения.

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

Стойкие объекты звучат очень медленно, но не обязательно, см. this и который для некоторых указателей. Также просмотрите этот и что в переполнении стека.

Также посмотрите retlang - на основе сообщений concurrency в .NET. Его пакетная обработка сообщений может быть полезна.

(Для WPF у меня была бы View-Model, которая устанавливает в потоке пользовательского интерфейса, который затем обновлялся в пакетах из многопоточной модели фоновым потоком. Однако WPF намного лучше сочетает события привязки данных затем WinForms.)

Ответ 2

Существует статья статьи MSDN, посвященная этой теме. Но будьте готовы взглянуть на VB.NET.;)

Кроме того, возможно, вы можете использовать System.ComponentModel.BackgroundWorker вместо общего второго потока, поскольку он красиво оформляет вид взаимодействия с порожденной которую вы описываете. Пример, приведенный в библиотеке MSDN, довольно приличный, поэтому посмотрите на него, чтобы узнать, как его использовать.

Изменить: Обратите внимание: марширование не требуется, если вы используете событие ProgressChanged для связи с потоком пользовательского интерфейса. Фоновый поток вызывает ReportProgress всякий раз, когда ему приходится общаться с пользовательским интерфейсом. Поскольку к этому событию можно прикрепить любой объект, нет никаких оснований для ручного сортировки. Прогресс сообщается через другую операцию async - поэтому нет необходимости беспокоиться ни о том, насколько быстро пользовательский интерфейс может обрабатывать события прогресса, ни если фоновый поток прерывается, ожидая завершения события.

Если вы докажете, что фоновый поток слишком быстро увеличивает ход события, то вы можете посмотреть Модели Pull против Push для обновлений пользовательского интерфейса отличная статья Айенде.

Ответ 3

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

Пользовательский интерфейс нужно обновлять столько раз в секунду, поэтому производительность никогда не будет проблемой, а опрос будет работать нормально

Мне нравится использовать граф объектов, который постоянно обновляется пулом фоновых потоков. Они проверяют фактические изменения значений данных, и когда они замечают фактическое изменение, они обновляют счетчик версий в корневой части графика объекта (или по каждому основному элементу, что имеет смысл) и обновляют значения

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

Этот простой метод полностью изолирует поток пользовательского интерфейса от фоновых потоков

Ответ 4

Я просто сражался с подобной ситуацией - поток badkground, обновляющий интерфейс через BeginInvokes. Фон имеет задержку в 10 мс на каждом цикле, но по дороге я столкнулся с проблемами, когда обновления пользовательского интерфейса, которые иногда запускаются каждый раз в этом цикле, не могут идти в ногу с частотой обновлений, и приложение эффективно прекращает работу (не уверен, что происходит - выдул стек?).

Я завершил добавление флага в объект, переданный над вызовом, который был только готовым флагом. Я установил бы это значение false перед вызовом invoke, а затем поток bg не будет делать больше обновлений ui, пока этот флаг не переключится на true. В потоке пользовательского интерфейса будут выполняться обновления экрана и т.д., А затем установите для этого var значение true.

Это позволило потоку bg продолжать хруст, но разрешал ui отключать поток, пока он не был готов к большему.

Ответ 5

Это проблема, которую я решил в Update Controls. Я предлагаю вам не предлагать вам переписать свой код, но дать вам источник для поиска идей.

Техника, которую я использовал в WPF, заключалась в том, чтобы использовать Dispatcher.BeginInvoke для уведомления об изменении переднего плана изменения. Вы можете сделать то же самое в Winforms с Control.BeginInvoke. К сожалению, вы должны передать ссылку на объект Form в свой объект данных.

Как только вы это сделаете, вы можете передать Action в BeginInvoke, который запускает PropertyChanged. Например:

_form.BeginInvoke(new Action(() => NotifyPropertyChanged(propertyName))) );

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

Ответ 6

Создайте новый UserControl, добавьте свой контроль и отформатируйте его (возможно, dock = fill) и добавьте свойство. теперь настройте свойство, чтобы вызвать usercontrol и обновить свой элемент, каждый раз, когда вы меняете форму свойства, любой поток, который вы хотите!

это мое решение:

    private long value;
    public long Value
    {
        get { return this.value; }
        set
        {
            this.value = value;

            UpdateTextBox();
        }
    }

    private delegate void Delegate();
    private void UpdateTextBox()
    {
        if (this.InvokeRequired)
        {
            this.Invoke(new Delegate(UpdateTextBox), new object[] {});
        }
        else
        {
            textBox1.Text = this.value.ToString();
        }
    }

в моей форме я привяжу свой взгляд

viewTx.DataBindings.Add(new Binding("Value", ptx.CounterTX, "ReturnValue"));

Ответ 7

Этот пост старый, но я думал, что дам варианты другим. Кажется, что как только вы начнете выполнять асинхронное программирование и привязку данных Windows Forms, вы получите проблемы с обновлением источника данных Bindingsource или обновлением списков, связанных с контролем форм Windows. Я попытаюсь использовать класс Jeffrey Richters AsyncEnumerator из своих инструментов powerthreading на wintellect.

Причина: 1. Свой класс AsyncEnumerator автоматически перенаправляет потоки фона в потоки пользовательского интерфейса, чтобы вы могли обновлять элементы управления, как это было бы при использовании синхронного кода. 2. AsyncEnumerator упрощает программирование Async. Он делает это автоматически, поэтому вы записываете свой код синхронно, но код по-прежнему работает асинхронно.

У Джеффри Рихтера есть видео на MSDN 9-го канала, что объясняет AsyncEnumerator.

Пожелайте мне удачи.

-R