C# 线程安全控件更新

C# thread safe control update

问题:当窗体及其控件从线程访问时,哪个更合适?:

  1. 调用表单并在表单调用中更新其控件

  2. 清晰地调用控件和清晰地更新控件

代码

if (mainForm.InvokeRequired)
{
   mainForm.Invoke(
      (Action)(() =>
                  {
                     mainForm.label1.Text = "update";
                     mainForm.textBox1.Text = "update";
                     mainForm.button1.Text = "update";
                  }));
}
else
{
   mainForm.label1.Text = "update";
   mainForm.textBox1.Text = "update";
   mainForm.button1.Text = "update";
}


//OR

if (mainForm.label1.InvokeRequired)
{
   mainForm.Invoke((Action)(() => { mainForm.label1.Text = "update"; }));
}
else
{
   mainForm.label1.Text = "update";
}

每次您 Invoke,都会有延迟和成本,因为我们必须向 UI 线程发送消息,本质上消息是 "please run this code"。然后我们必须等待 UI 线程接收此消息(它当前可能正忙于做一些其他工作),运行 我们要求它接收的代码,并等待它指示就这样完成了。然后,最后,我们可以在 Invoke 通话后继续工作。

现在,在宏伟的计划中,这个成本是相当低的。因此,如果您只是使用 Invoke 一次或两次,则不值得考虑。但是,如果您计划执行一个大循环并在循环内 Invokeing,您可能需要暂停并重新考虑。通常最好将 Invoke 移到循环之外。

但是,UI线程是一种宝贵的资源。我们不想在该线程上花费时间来执行 UI 相关任务。

因此,如果您有一个循环同时执行 CPU 密集型工作和 UI 更新,则两者都不一定合适。在这种情况下,看看您是否可以重新构建代码,以便在循环内准备 批次 的 UI 更新,然后偶尔(或在循环之后,如果可能)做一个 Invoke 来执行那些 UI 更新。

在您的情况下,您似乎已经有一批 UI 更新要执行。所以更喜欢 Invoke 一次完成所有三个的代码:

if (mainForm.InvokeRequired)
{
   mainForm.Invoke(
      (Action)(() =>
                  {
                     mainForm.label1.Text = "update";
                     mainForm.textBox1.Text = "update";
                     mainForm.button1.Text = "update";
                  }));
}
else
{
   mainForm.label1.Text = "update";
   mainForm.textBox1.Text = "update";
   mainForm.button1.Text = "update";
}

但请注意,通常最好构建代码,这样您就不会 复制 上面的实际 UI 更新。类似于:

private void DoUpdate()
{
   if(mainForm.InvokeRequired)
   {
      mainForm.Invoke(DoUpdate);
      return;
   }
   mainForm.label1.Text = "update";
   mainForm.textBox1.Text = "update";
   mainForm.button1.Text = "update";
}

我通常使用ISynchronizeInvoke因为所有forms/usercontrols都继承了这个接口,然后调用一个方法在ui线程上执行你的动作(如果调用是requi红色),

 public void SafeExecute(IView form, Action methodToExecute)
 {
      // args chek....
      ISynchronizeInvoke view = form as ISynchronizeInvoke;
      if(view != null)
      {
        if (!View.InvokeRequired)
            {
                methodToExecute();
            }
            else
            {
                view.Invoke(methodToExecute, null);
            }
      }
      else methodToExecute();
 }

然后在你的代码隐藏中你可以像这样调用这个方法(我假设你的视图实现了一个接口,在这种情况下我称之为 IView)

 SafeExecute(this, () => {
   mainForm.label1.Text = "update";
   mainForm.textBox1.Text = "update";
   mainForm.button1.Text = "update";
 })