WinForms线程安全控件



我花了很多时间使用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();
}

您可以用委托来包装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<T>委托是.NET 2.0的一部分,因此只要您是VS2008或更高版本,它就会根据.NET Framework 2.0进行编译。

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

1) 我想"简化"我的代码,如果最重要的发现之一是:

control.InvokeRequired

真的不需要。。。这几乎是既定的。重要的是,如果您在后台(或非UI)线程中,则需要调用控件。

2) 调用在控制树上向上移动,因此如果您有:

窗体>控件>控件内部控件>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
    }
}

既然您已经在使用后台工作程序,为什么不"滥用"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;
   }
}

最新更新