Windows Forms & Invoke from parallel threads

в 16:56, , рубрики: .net, WinForms

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

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

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

И все хорошо до момента, пока наш background поток не попытается создать еще один-два-… Которым делегирует дополнительную работу в рамках задачи. Вот с этими-то новыми потоками и начинается чехарда…

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

Windows Forms & Invoke from parallel threads

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 начинает вести не совсем так, как ожидалось. Сообщения приходят и форма их получат, вот только складывает их в очередь для обновления контента и пока ваши background потоки не закончат работу, не спешит выводить на экран. И чем более сложной задачей заняты вы в других потоках, тем нагляднее это проявляется. Причем в реальном коде вам наверняка надо будет не только один Label изменить, вполне возможно, что набор данных для смены визуализации будет куда как более сложным.

Windows Forms & Invoke from parallel threads

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

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

Fields:

    private readonly SynchronizationContext syncContext;

Constructor:

   syncContext = SynchronizationContext.Current;

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

private void SendNotificationToForm( string actionText )
{
   syncContext.Post( UpdateUI, actionText);
}

private void UpdateUI(actionText)
{
   this.LabelInfo.Text = actionText;
}

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

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

Автор: mynameiszb

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js