代码改变世界

使用匿名委托,Lambda简化多线程代码

2011-11-09 13:46  elivsit  阅读(210)  评论(0编辑  收藏  举报

.net中的线程也接触不少了。在多线程中最常见的应用莫过于有一个耗时的操作需要放到线程中去操作,而在这个线程中我们需要更新UI,这个时候就要创建一个委托了来更新UI了,不然会报错的。下面我们就来设计一个简单的场景:窗体上有一个按钮和进度条,按钮按下后启动一个线程让进度条滚动。需要说明一下的是,我们这里不讨论使用匿名委托,lambda的好坏,我们只有一个目标就是使得我们的程序:短点,短点,再短点。

 

最“朴素”写法

 

      我刚开始没有掌握匿名方法这些就是按照下面这些写的,说实话很痛苦。后来接触了匿名表达式,lambda后几乎都不想再想写这样的东西了,除非特殊的一些情况,比如需要自己定义委托。如果您现在还在按照下面这样写,那么这篇文章对你或许有些帮助!

        //声明一个委托        delegate void UpdateProgressDelegate();        //声明一个UpdateProgressDelegate的委托实例        private UpdateProgressDelegate UpdateProgressHandle;        public Form1()        {            InitializeComponent();            progressBar1.Maximum = 100;            progressBar1.Minimum = 0;            progressBar1.Value = 0;            //将该委托实例和UpdateProgressValue方法绑定起来            UpdateProgressHandle = new UpdateProgressDelegate(UpdateProgressValue);        }        private void button1_Click(object sender, EventArgs e)        {            Thread t = new Thread(new ThreadStart(D));            t.Start();        }        private void D()        {            //其他事情             //..........            progressBar1.Invoke(UpdateProgressHandle);  //调用Invoke更新进度条,参数是我们新建的委托         }        //更新进度条的方法        private void UpdateProgressValue()        {            for (int i = 0; i < 50; i++)            {                progressBar1.Value = i;            }        }    
去掉委托创建

      这个写法基本就是.net 1.x里面的委托写法了。您也看出来,很繁琐。像一个多线程的界面中,要和多线程打交道的控件何止一个两个。几个一来就会感觉很烦了。维护起来也不方便。下面我们使用.net 2.0中的一个新特性:支持省略委托的创建,直接将方法名字赋给需要的参数。即我们可以将

Thread t = new Thread(new ThreadStart(D));

改为:

Thread t = new Thread(D);

      虽然只是少了一点,不过好歹也是个进步对吧。

去掉自定义方法

    现在我们引入.net 2.0中的匿名委托来改善下上面这个程序,使其看起来更加简洁点。怎样使用匿名委托?教你个简单的方法,程序中参数是方法名字的地方您都可以通过delegate(){//操作}的形式来代换。比如下面我们就像D方法名那里给替换掉。

        //声明一个委托        delegate void UpdateProgressDelegate();        //声明一个UpdateProgressDelegate的委托实例        private UpdateProgressDelegate UpdateProgressHandle;        public Form1()        {            InitializeComponent();            progressBar1.Maximum = 100;            progressBar1.Minimum = 0;            progressBar1.Value = 0;            //将该委托实例和UpdateProgressValue方法绑定起来            UpdateProgressHandle = new UpdateProgressDelegate(UpdateProgressValue);        }        private void button1_Click(object sender, EventArgs e)        {            Thread t = new Thread(delegate() { progressBar1.Invoke(UpdateProgressHandle); });            t.Start();        }        //更新进度条的方法        private void UpdateProgressValue()        {            for (int i = 0; i < 50; i++)            {                progressBar1.Value = i;            }        }

 

我们将D方法删除,将Thread线程声明的时候直接改为使用匿名委托来定义需要执行的操作。怎么样?整整少了一个方法的定义,同时操作更加接近使用的地方了。不过我还是觉得太多了,还能去掉一点吗?当然能。
 

 

去掉自定义委托

 

      我们在上面我们定义的UpdateProgressDelegate委托上下功夫。能不能直接不用声明就可以使用呢?这时我们就需要使用Action<T>,Func<T,ResultT>委托了。这两个是系统自带的委托,利用这两个现成的委托我们可以省去自定义简单委托的步骤。这两个委托的区别在于Action<T>只有参数没有返回值,而Func<T,ResultT>既有参数也有返回值。里面的T代表了你要执行的方法的参数类型。另外需要注意的是,在.net framework 2.0中只有Action<T>一种形式,在3.5中增加了Action(无参数形式)以及Action<T1,T2>等最多四个参数,Func<T,ResultT>,Func<T1,T2,ResultT>最多四个参数。所以如果您的.net版本是2.0那么意味着您只有Action<T>可以使用。我们现在首先在.net 3.5下用Action委托来简化上面的代码。 形式如下:
    public partial class Form1 : Form    {        public Form1()        {            InitializeComponent();            progressBar1.Maximum = 100;            progressBar1.Minimum = 0;            progressBar1.Value = 0;        }        private void button1_Click(object sender, EventArgs e)        {            Thread t = new Thread(delegate() { progressBar1.Invoke(new Action(UpdateProgressValue)); });            t.Start();        }        //更新进度条的方法        private void UpdateProgressValue()        {            for (int i = 0; i < 50; i++)            {                progressBar1.Value = i;            }        }    }

 

 

 

可以看到我们之前声明的那一长段的委托都去掉了,清爽了不少。那么在.net 2中该如何使用呢?两个办法:

1.强制在UpdateProgressValue中加个参数,但我们不使用。代码如下:

代码

 

 

2.不使用Action委托,还记得我们最朴素写法中的ThreadStart这个委托吗?这个就是一个现成的无参数委托,不用白不用!代码如下:

代码

上面我们已经说过了,有方法名为参数的地方可以使用匿名方法替代,那么上面的那个new Action(UpdateProgressValue)中的UpdateProgressValue我们同样可以再给替换掉了。代码如下:

代码
 
怎么样,这下子就在一个方法里面搞定所有的事情了,这样写起来是不是比你定义许多委托啊什么的爽多了。不过我们的旅程还没有结束,还有减少的空间。
终极简化---使用Lambda

      我们最后引入lambda来简化我们的代码。Lambda表达式在C#中的写法是“arg-list => expr-body”,“=>”符号左边为表达式的参数列表,右边则是表达式体(body)。参数列表可以包含0到多个参数,参数之间使用逗号分割。当然因为我们这里没有参数所有可以直接写成()=>{}的形式了啦。lambda用在哪里呢?它可以替换匿名表达式使其更加简单,在LINQ等等查询语句中也有使用,不过不是我们今天讨论的范围。如何替换匿名表达式呢?代码如下:

        private void button1_Click(object sender, EventArgs e)        {            Thread t = new Thread(() => progressBar1.Invoke(new Action(()=>                                                                                {                                                                                    for (int i = 0; i < 50; i++)                                                                                    {                                                                                        progressBar1.Value = i;                                                                                    }                                                                                })));            t.Start();        }

 

可以看到使用了lambda后匿名方法中的一些不需要的{}被省略了,自然看起来也就爽多啦。如果非要总结一个怎么替换的过程的话那就简单的认为将delegate(){}替换为了()=>{},如果有参数类似。

 

最后需要说的是别看我们上面的代码样子好像变化了不少,其实在编译后编译器会为我们上面省略的一系列代码再加上去的。有兴趣的可以看看简化后的IL和没有简化的IL,其实都是差不多的。好了,这就是我目前能达到的最短代码了。如果您还能再短些欢迎提出!