C#线程安全控件更新

时间:2018-11-02 06:14:40

标签: c# winforms thread-safety invoke

问题:从线程访问表单及其控件时,哪个更合适?:

  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";
}

2 个答案:

答案 0 :(得分:0)

每次Invoke时,都会存在延迟和成本,因为我们必须向UI线程发送消息,该消息实质上是“请运行此代码”。然后,我们必须等待UI线程接收到此消息(当前可能正在忙于其他工作),运行我们要求它执行的代码,并指示它已经完成了。然后,最后,我们可以在Invoke调用之后恢复工作。

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

但是,UI线程是宝贵的资源。我们不想花时间在该线程上执行 non 与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";
}

答案 1 :(得分:0)

我通常使用ISynchronizeInvoke,因为所有表单/用户控件都继承了此接口,然后调用一种方法来对ui线程执行操作(如果需要调用),

 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";
 })