Как безопасно обновить пользовательский интерфейс WinForm из фонового потока?
У меня есть 2 winforms:
- Form1
- Form2
Form1 является основной формой. Форма 1 открывает форму 2. В форме2 Load
обработчик событий, новый фоновый рабочий поток запущен. Когда рабочий поток завершает свою работу, он уведомляет поток пользовательского интерфейса об обновлении Form2.
Вопрос в том, что пользователь может закрыть Form2, пока рабочий поток еще работает. Таким образом, Form2 может исчезнуть к моменту окончания рабочего потока. Затем возникает некоторое исключение Null Reference, когда рабочий поток пытается обновить пользовательский интерфейс Form2.
Я планировал использовать флаг, чтобы указать существование Form2. Каждый раз при обновлении пользовательского интерфейса флаг проверяется, чтобы убедиться, что Form2 существует. Но эта схема проверки и действия не может справиться с расой. Потому что форма может быть закрыта после прохождения проверки, но до того, как будут предприняты действия по обновлению пользовательского интерфейса.
Так есть ли способ решить эту проблему?
Некоторый код формы 2:
private void StartComputeGraphWorker()
{// this runs on the UI thread.
try
{
this.generationFinished = false;
DisableAllControls(); //prevent user input while some background work is underway.
StartShowProgressMarquee();
ThreadStart d = new ThreadStart(WorkerStartWrapper);
worker = new Thread(d);
worker.IsBackground = true;
worker.Start();
}
catch (Exception ex)
{
Logger.LogMessage(Logger.LogLevel.Error, ex.Message);
EnableAllControls();
StopShowProgressMarquee();
}
}
private void NotifyUI(Boolean suceess)
{
if (suceess)
{
// this is on the secondary illustration form. it may NOT exist by now.
if (!this.formClosed)
{//race conditions here
this.Invoke(new MethodInvoker(ShowGraphDataInUIThread));
}
else//the form has been closed, we have no place to show the graph, just return.
{
return;
}
}
else
{
// this logs to the main input form, it always exists.
Logger.LogMessage(Logger.LogLevel.Warning, "Graph generation failed.");
}
}
private void WorkerStartWrapper()
{
try
{
RenderGraphWorker();
NotifyUI(true);
}
catch (Exception ex) // ThreadAbortException or Other Exceptions
{
Logger.LogMessage(Logger.LogLevel.Warning, ex.Message);
NotifyUI(false);
}
}
ДОБАВИТЬ 1
Я проверил ниже темы:
Как обновить графический интерфейс из другого потока в C#?
Это не совсем то же самое. Моя форма может исчезнуть. Это не только обновление управления несколькими потоками.
С подходом BackgroundWorker отмена подписки на событие RunWorkerCompleted в событии закрытия формы Form2 может решить мою проблему.
Но мне все еще интересно, возможно ли это с помощью класса Thread.
1 ответ
Сегодня я переосмысливаю подход отписки. Это выглядит хорошо. Но на самом деле, возможно, нет.
Есть условие гонки, что код может выполняться в обработчике завершенного события, пока я отписываюсь. Поэтому отказ от подписки не помешает ему манипулировать, возможно, несуществующей формой.
Я думаю, что я все еще должен придерживаться стандартной парадигмы BGW и решить эту проблему другим путем.
В моем сценарии у пользователя есть 2 способа отменить операцию BGW.
- Нажмите на
Cancel
кнопка. - Закройте форму.
Мое текущее решение:
Если пользователь нажмет Cacncel
кнопку, я покажу некоторые уведомления пользовательского интерфейса в Cancel
обработчик нажатия кнопки перед вызовом bgw.CancelAsync()
, Что-то вроде этого:
this.label1.Text = "Operation Cancelled";
bgw.CancelAsync()
В настоящее время пользовательский интерфейс гарантированно существует.
Если пользователь закроет форму, я просто позвоню bgw.CancelAsync()
в обработчике событий закрытия формы. Тогда BGW.DoWork()
будет опросить и найти этот сигнал и остановить выполнение. Мне не нужно уведомление пользователя интерфейса здесь, потому что это неявное намерение пользователя.
Для обоих сценариев отмены обработчик события BGW complete не содержит манипуляций с пользовательским интерфейсом для отмененного результата.
Таким образом, чтобы подвести итог, оставьте BGW, чтобы завершить свой жизненный цикл.