При переделке старой формы столкнулся с забавной проблемой.
Задача классическая: отобразить пользователю информацию о процессе, происходящем в фоновом режиме.
Казалось бы, ничего сложного.
В основной форме мы запускаем поток, обрабатываем в нем данные и при получении новых статусов сбрасываем обновление формы, синхронизируясь с базовым UI Thread (вызов Invoke/BeginInvoke).
И всё хорошо, пока наш фоновый поток не попытается создать ещё один-два.
которым он делегирует дополнительную работу внутри задачи.
Именно с этих новых потоков и начинается чехарда.
Итак, первый поток получил текст для обновления; мы отобразим этот текст в форме.
или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, вполне возможно, что набор данных для изменения визуализации будет гораздо сложнее.
В официальной документации по этой теме упорно говорится об 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
-
Холли, Роберт Уильям
19 Oct, 24 -
Предварительная Обработка Данных
19 Oct, 24 -
Gmail Обновлен
19 Oct, 24