【问题标题】:WinForms Thread-safe ControlsWinForms 线程安全控件
【发布时间】:2013-08-05 20:57:08
【问题描述】:

我花了很多时间处理 Windows 窗体控件,但从后台工作线程开始 - 我认为这是一种很好的做法,因为您不希望在人们单击按钮时窗体被锁定。老实说,我通常在后台工作线程中执行几乎所有与 GUI 相关的操作,因此界面对用户的响应很好(希望更多人会这样做!)。

所以我的问题是……每次我必须与控件交互时,我都必须“调用”它们,例如:

if (control.InvokeRequired)
{
    //
}

标准做法对吗?但是,这会导致我编写一些非常混乱的代码,因为几乎我拥有的每种控件类型都需要 MethodInvoker 委托或其他东西。它在我的保护中添加了数千行代码,而且非常耗时。

我目前有数百种“属性设置”方法,例如:

private void Safe_SetLableText(Label control, string text)
{
    if (control.InvokeRequired)
    {
        control.Invoke((MethodInvoker)delegate
        {
            control.Text = text;
        });
    }
    else
    {
        control.Text = text;
    }
}

那么,是否有其他技术或方法可以做到这一点,或者有什么方法能够始终改变控件的属性,无论控件是什么,无论我在哪个线程中?

类似:(伪代码)

BackgroundWorker.RunWorkerAsync();

private void thing_to_do()
{
    // We are in a background thread now

    DoSomeDatabaseWorkThatTakesALongTime();

    InvokeAnyControls();

    // Do some stuff...
    controlX.Text = "123"
    controlY.Height = 300;
    controlZ.text = ControlA.text;

    RestoreAnyControls();
}

【问题讨论】:

  • 您使用的是哪个版本的 C#? C# 5 中新的异步特性应该能够解决很多这样的问题。
  • 我们使用 .NET 2 作为公司范围内的标准。
  • @JonSkeet async 有什么帮助?我很好奇,这听起来很吸引人。
  • @DavidHeffernan:如果所有长时间运行的操作都可以异步执行,那么OP的方法实际上可以all在UI线程中运行,并且只是异步执行IO等。

标签: multithreading winforms controls


【解决方案1】:

我发布我自己问题的答案,因为我认为它会为社区增加价值。

1) 我想“简化”我的代码,如果最重要的发现是:

control.InvokeRequired

真的不需要......它几乎是给定的。重要的是,如果您在后台(或非 UI)线程中,您可以依赖需要调用控件的事实。

2) 调用“向上”传播控制树,所以如果你有:

Form > Control > Control inside Control > etc > etc

你只需要调用“Form”(最上面),然后你就可以改变子元素的属性了。

所以这是我与后台工作人员(或非 UI 线程)一起工作的干净简单的解决方案。我刚刚对此进行了测试,效果很好。

public partial class Form1: Form
{
    public Form1()
    {
        BackgroundWorker bgw = new BackgroundWorker();
        bgw.DoWork += new DoWorkEventHandler(this.bgDoWork);
        bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(this.bgComplete);
        bgw.RunWorkerAsync();
    }

    private void bgComplete(object sender, EventArgs e)
    {
        // You are not in the UI thread now, so you can Invoke without error
        this.Invoke((MethodInvoker)delegate
        {
            // Now you can change any property an any control within this form.
            // Remember "this" refers to Form1.
            this.label1.Text = "test123";
            this.label2.Text = "test456";
            this.label3.Text = this.label4.Text;

            // You can set progress bars too, not just label text
        }
    }

    private void bgDoWork(object sender, DoWorkEventArgs e)
    {
        // Do something that takes a long time
    }
}

【讨论】:

    【解决方案2】:

    既然您已经在使用后台工作人员,为什么不“滥用”OnProgressChanged

    private void thing_to_do()
    {
        // We are in a background thread now
        DoSomeDatabaseWorkThatTakesALongTime();
    
        BackgroundWorker.ReportProgress(1, "state");
    
        DoSomeMoreDatabaseWorkThatTakesALongTime();
    
        BackgroundWorker.ReportProgress(2, YourObjectHere);
    }
    
    void OnProgressChanged(ProgressChangedEventArgs progressArgs)
    {
       switch(progressArgs.ProgressPercentage)
       {
           case 1:
              // Do some stuff...
              controlX.Text = "123"
              controlY.Height = 300;
              controlZ.text = ControlA.text;
           break;
           case 2:
              // other stuff
              YourObject obj = (YourObject) progressArgs.UserState;
              // wahtever...
           break;
           default:
           break;
       }
    
    }
    

    【讨论】:

      【解决方案3】:

      您可以使用委托包装您的 InvokeRequired 代码,如下所示:

      public static void Invoke2<TControl>(this TControl c, Action<TControl> code) where TControl : Control {
      
          if( c.InvokeRequired ) c.Invoke( delegate() { code(c); } );
          else code(c);
      }
      

      然后像这样使用它:

      private void Safe_SetLableText(Label control, string text) {
          control.Invoke2( c => c.Text = text );
      }
      

      当然,你可能想要比Invoke2 更好的名字,但我希望你能接受这个想法。请注意,lambda 表达式语法是 C# 3.0 的一项功能,但 Action&lt;T&gt; 委托是 .NET 2.0 的一部分,因此只要您是 VS2008 或更高版本,它将针对 .NET Framework 2.0 进行编译。

      【讨论】:

      • 也许值得展示一个带有语句 lambda 的示例,以便在一个块中设置多个属性。
      • 这与我现有的情况大致相同,我必须为要设置的每个属性都有一个方法。我不确定您是否可以可靠地将标签转换为通用“控件”?我有点在寻找一种万能的解决方案。
      • @user2542014 这是一个万能的解决方案:看到包装扩展方法是通用的,并且会强类型任何Control 子类,所以你的方法Safe_SetLableText 实际上是不必要的.
      • 谢谢,在测试你的技术后,你是对的,所以我很高兴为你提供解决方案。然而,我也将提供一些我自己的反馈,因为我想要实现的一件事是简化我的代码,正如您将在下面看到的那样 - 非常重要。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-03
      • 2014-10-12
      • 2010-11-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多