WinForm 的线程安全访问[转]

来源:互联网 发布:php就业如何 编辑:程序博客网 时间:2024/05/22 13:50
WinForm 的线程安全的访问

用C#写Windows程序少不了要写WinForm程序。很多时候,我们还需要写多线程的WinForm应用。最典型的就是为了不因为核心代码执行影响用户对应用程序的响应,当执行一个比较耗时的操作时,为了方式用户界面死掉的情况,常需要建立一个背景线程去运行耗时的代码,并且实时将结果表现在当前窗口上。

在多线程访问WinForm的时候,我们会注意到,WinForm的那些Control不是线程安全的。因此不建议直接访问它们,否则会导致竞争冒险,甚至可能出现死锁。比如在线程中直接使用下面的代码就是不推荐的:

textBox1.Text = “OK”;
微软建议的办法,使建立一个SetTextBox(string text)的函数,里面写上上面这句。然后判断this.InvokeRequired,如果需要,就调用this.Invoke(SetTextBox, “OK”);,否则直接调用textBox1.Text = “OK”。

总结一下会发现,这个建议的方法不合理,因为同样是textBox1.Text = “OK”; 在不同的位置上被写了两次。两次?惊醒。凡是同一个东西,在不同的位置出现了两次,我们就需要惊醒了。很多安全问题都是由于这种两个不同位置表达一个意思而造成。如果修改了一个地方,而忘了修改另一个地方怎么办?如果是数据的话,还会出现,用户代码到底会用哪个数据作为基准?虽然程序员在“尽量”保证两个位置一致,但是历史已经无数次告诉我们这种“尽量”非常不可靠。那么我们如何解决这个问题呢?这次匿名方法和匿名委托又一次显身手了。

首先,我们定义一个线程安全的访问Control的函数 DoThreadSafe():

        private static void DoThreadSafe(Control control, MethodInvoker function)
        ...{
            if (function != null)
            ...{
                if (control.InvokeRequired)
                ...{
                    control.Invoke(function);
                }
                else
                ...{
                    function();
                }
            }
        }
这里的代码实现了微软推荐的采用InvokeRequired判断,然后通过Invoke()调用具体操作的逻辑,但是通过入口的MethodInvoker function函数参数避免了同一个东西被写两次的情况。这里MethodInvoker是System.Windows.Forms名字空间下的一个delegate,和上面的NoArgumentHandler定义一样:

delegate void MethodInvoker();
有了这个小小的帮助函数,我们写线程安全的 WinForm 操作就很简单了,比如,这回我们需要设置进度条的Value:

 

        private void SetProgressBar(int value)
        ...{
            DoThreadSafe(progressBar1, delegate
            ...{
                progressBar1.Value = value;
            });
        }
 

凡是需要访问 WinForm 空间,我们都包裹上这么一行代码,就能够保证对WinForm所作的操作时线程安全的了,很方便。

如果进一步注意,我们会发现,虽然我们的delegate是无参数传递的,但是,在上面的调用代码里面,prograssBar1.Value = value,这个value是delegate外的函数地参数。神奇吧?这是合法的,虽然名义上,这是一个匿名方法,已经不属于当前scope了,但是依旧可以访问当前scope里面的变量,这就给我们很大的便利,我们可以充分利用这一点,而不再需要定义各种各样的有参数的delegate来完成对不同控件所需要的线程安全的操作。呵呵,否则,按照微软的建议,那几乎是要对WinForm上每一个元素都做一个SetXxxxxText(string text), GetXxxxxText()函数了。

这个小小的DoThreadSafe(),大大降低了对那些Delegate的需求,并且利用匿名方法(anonymous methods)大大减少了声明函数的工作量。(当然实际上编译器在生成执行代码的时候会帮你自动产生对应的函数,不信你就用Reflector来看看。)

3、总结
匿名方法可以降低另写method的工作量,而且匿名方法可以访问调用者同scope的变量,利用这点我们可以大大简化委托的声明,和降低传参的复杂度。

好好的利用匿名方法和匿名委托,会让你的代码看起来更加优雅,优雅的代码也会降低错误发生的可能性。

 
原创粉丝点击