如何从后台线程安全地更新 WinForm UI?
How to safely update the WinForm UI from a background thread?
我有 2 个 winforms:
- 表格 1
- Form2
Form1 是主窗体。 Form1 打开 Form2。在 Form2 Load
事件处理程序中,启动了一个新的后台工作线程。当工作线程完成后,它会通知 UI 线程更新 Form2。
问题是,用户可以在工作线程仍在 运行 时关闭 Form2。因此 Form2 可能会在工作线程完成时消失。然后,当工作线程尝试更新 Form2 UI.
时,会发生一些 Null Reference Exception
我打算用一个标志来表示 Form2 的存在。每次更新 UI 时,都会检查标志以确保 Form2 存在。但是这种 check-and-act 模式无法处理竞争条件。因为表单可能会在 after 之后关闭,所以检查通过了,但是 before 采取了 UI 更新操作。
那么有什么办法可以解决这个问题吗?
Form2部分代码:
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
我检查了以下线程:
How to update the GUI from another thread in C#?
不完全一样。我的表格可以消失了。这不仅仅是关于跨线程控制更新。
采用BackgroundWorker方式,取消订阅Form2 Closing事件中的RunWorkerCompleted事件即可解决我的问题。
但我仍然想知道是否可以使用 Thread class。
今天,我重新考虑了 取消订阅 方法。看起来不错。但实际上可能不是。
存在竞争条件,当我取消订阅时,代码可能在已完成的事件处理程序中 运行。 所以取消订阅不会阻止它操纵可能不存在的表单。
我认为我仍然应该坚持标准的 BGW 范例并以其他方式解决这个问题。
在我的场景中,用户有两种方法可以取消 BGW 操作。
- 单击
Cancel
按钮。
- 关闭表单。
我目前的解决方案是:
如果用户单击 Cacncel
按钮,我将在 Cancel
按钮单击处理程序中显示一些 UI 通知 before 调用 bgw.CancelAsync()
。像这样:
this.label1.Text = "Operation Cancelled";
bgw.CancelAsync()
此时,UI保证存在。
如果用户关闭表单,我只调用表单关闭事件处理程序中的bgw.CancelAsync()
。然后 BGW.DoWork()
将轮询并找到此信号并停止执行。我需要不 UI通知用户,因为这是用户的隐含意图。
对于这两种取消场景,BGW 完成事件处理程序不包含对取消结果的 UI 操作。
总而言之,让 BGW 完成其生命周期。
我有 2 个 winforms:
- 表格 1
- Form2
Form1 是主窗体。 Form1 打开 Form2。在 Form2 Load
事件处理程序中,启动了一个新的后台工作线程。当工作线程完成后,它会通知 UI 线程更新 Form2。
问题是,用户可以在工作线程仍在 运行 时关闭 Form2。因此 Form2 可能会在工作线程完成时消失。然后,当工作线程尝试更新 Form2 UI.
时,会发生一些 Null Reference Exception我打算用一个标志来表示 Form2 的存在。每次更新 UI 时,都会检查标志以确保 Form2 存在。但是这种 check-and-act 模式无法处理竞争条件。因为表单可能会在 after 之后关闭,所以检查通过了,但是 before 采取了 UI 更新操作。
那么有什么办法可以解决这个问题吗?
Form2部分代码:
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
我检查了以下线程:
How to update the GUI from another thread in C#?
不完全一样。我的表格可以消失了。这不仅仅是关于跨线程控制更新。
采用BackgroundWorker方式,取消订阅Form2 Closing事件中的RunWorkerCompleted事件即可解决我的问题。
但我仍然想知道是否可以使用 Thread class。
今天,我重新考虑了 取消订阅 方法。看起来不错。但实际上可能不是。
存在竞争条件,当我取消订阅时,代码可能在已完成的事件处理程序中 运行。 所以取消订阅不会阻止它操纵可能不存在的表单。
我认为我仍然应该坚持标准的 BGW 范例并以其他方式解决这个问题。
在我的场景中,用户有两种方法可以取消 BGW 操作。
- 单击
Cancel
按钮。 - 关闭表单。
我目前的解决方案是:
如果用户单击 Cacncel
按钮,我将在 Cancel
按钮单击处理程序中显示一些 UI 通知 before 调用 bgw.CancelAsync()
。像这样:
this.label1.Text = "Operation Cancelled";
bgw.CancelAsync()
此时,UI保证存在。
如果用户关闭表单,我只调用表单关闭事件处理程序中的bgw.CancelAsync()
。然后 BGW.DoWork()
将轮询并找到此信号并停止执行。我需要不 UI通知用户,因为这是用户的隐含意图。
对于这两种取消场景,BGW 完成事件处理程序不包含对取消结果的 UI 操作。
总而言之,让 BGW 完成其生命周期。