如何从后台线程安全地更新WinForm UI?

时间:2017-08-29 08:13:22

标签: c# multithreading winforms race-condition

我有2个winforms:

  • Form1中
  • 窗体2

Form1是主要形式。 Form1打开Form2。在Form2 Load事件处理程序中,启动新的后台工作线程。当工作线程完成时,它将通知UI线程更新Form2。

问题是,用户可以在工作线程仍在运行时关闭Form2 。因此Form2可能会在工作线程结束时消失。然后,当工作线程尝试更新Form2 UI时,会发生一些Null Reference Exception。

我打算用一个标志来表示Form2的存在。每次更新UI时,都会检查标志以确保Form2存在。但是这个 check-and-act 模式无法处理竞赛规则。因为表单可能会在之后关闭,但检查通过但 之前会执行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);
    }
}

ADD 1

我检查了下面的帖子:

How to update the GUI from another thread in C#?

它并不完全相同。我的表格可以不见了。它不仅仅是关于跨线程控制更新。

使用BackgroundWorker方法,在Form2 Closing事件中取消订阅RunWorkerCompleted事件可以解决我的问题。

但我仍然想知道是否可以使用Thread类。

1 个答案:

答案 0 :(得分:0)

今天,我重新考虑取消订阅方法。看起来不错。但实际上可能不是。

竞争条件,在我取消订阅时,代码可能正在已完成的事件处理程序中运行。 因此,取消它不会阻止它操纵可能不存在的形式。

我认为我仍然应该坚持标准的BGW范式并以其他方式解决这个问题。

在我的场景中,用户有2种方法可以取消BGW操作。

  • 点击Cancel按钮。
  • 关闭表单。

我目前的解决方案是:

如果用户点击Cacncel按钮,我会在Cancel按钮点击处理程序之前显示一些用户界面通知,然后再调用bgw.CancelAsync()。像这样:

this.label1.Text = "Operation Cancelled";
bgw.CancelAsync()

此时,用户界面 guarenteed 存在。

如果用户关闭表单,我只需在表单结束事件处理程序中调用bgw.CancelAsync()。然后BGW.DoWork()将轮询并找到此信号并停止执行。我在这里需要 no 用户界面通知,因为这是用户的隐含意图

对于两种取消方案,BGW完整事件处理程序包含对取消结果无UI操作。

总而言之,让BGW完成其生命周期。