WinForms线程安全控件

时间:2013-08-05 08:02:03

标签: multithreading winforms controls

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

3 个答案:

答案 0 :(得分: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<T>委托是.NET 2.0的一部分,因此只要您是VS2008或更高版本,这将针对.NET Framework 2.0进行编译。 p>

答案 1 :(得分:1)

我正在回答我自己的问题,因为我认为它会为社区增加价值。

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

control.InvokeRequired

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

2)调用将“向上”移动到控制树,所以如果你有:

表格&gt;控制&gt;控制内部控制&gt;等等&gt;等

您只需要调用“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 :(得分:0)

由于您已经在使用背景工作人员,为什么不要滥用&#39; 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;
   }

}