Windows Forms И Вызов Из Параллельных Потоков

При переделке старой формы столкнулся с забавной проблемой.

Задача классическая: отобразить пользователю информацию о процессе, происходящем в фоновом режиме.

Казалось бы, ничего сложного.

В основной форме мы запускаем поток, обрабатываем в нем данные и при получении новых статусов сбрасываем обновление формы, синхронизируясь с базовым UI Thread (вызов Invoke/BeginInvoke).

И всё хорошо, пока наш фоновый поток не попытается создать ещё один-два.

которым он делегирует дополнительную работу внутри задачи.

Именно с этих новых потоков и начинается чехарда.

Итак, первый поток получил текст для обновления; мы отобразим этот текст в форме.



Windows Forms и вызов из параллельных потоков

  
  
  
   

private delegate void EditStatusTextDelegate(string strArg); private void SendNotificationToForm( string actionText ) { if (this.InvokeRequired) { this.Invoke(new EditStatusTextDelegate(UpdateUI), new object[] { actionText }); return; } UpdateUI(actionText); } private void UpdateUI(actionText) { this.LabelInfo.Text = actionText; }

или

private void SendNotificationToForm( string actionText ) { this.BeginInvoke((Action)(() => { this.LabelInfo.Text = actionText; })); }

Это работает в рамках одного потока — приходят сообщения, форма их получает, данные обновляются.

Когда мы добавляем еще несколько, наша функция SendNotificationToForm начинает вести себя не совсем так, как ожидалось.

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

И чем более сложной задачей вы занимаетесь в других тредах, тем ярче это проявляется.

Более того, в реальном коде вам, вероятно, потребуется изменить не только одну Label, вполне возможно, что набор данных для изменения визуализации будет гораздо сложнее.



Windows Forms и вызов из параллельных потоков

В официальной документации по этой теме упорно говорится об InfokeRequired & BeginInvoke. Но на самом деле в нашем случае нам придется отказаться от «обычного метода» и перейти на ручное управление контекстом синхронизации.

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

Для этого после создания формы (когда контекст уже создан и инициализирован) запомните ее:

Fields: private readonly SynchronizationContext syncContext; Constructor: syncContext = SynchronizationContext.Current;

И теперь в нашем обратном вызове из другого потока мы выполним команду:

private void SendNotificationToForm( string actionText ) { syncContext.Post( UpdateUI, actionText); } private void UpdateUI(actionText) { this.LabelInfo.Text = actionText; }

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

С точки зрения проблемы решение простое, но эффективное.

Зато оно гарантирует, что теперь пользователь увидит на форме всю информацию из запущенных потоков, а не только часть данных из первого.

Теги: #.

NET #winforms #.

NET

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.