日期:2014-05-17  浏览次数:21238 次

C# 使用委托跨线程通讯

????? 当我们需要处理大量数据时,为了使UI界面不致出现假死状态,我们就必须使用多线程进行处理。所以问题就出现了,我们都知道线程作为一个独立运行的单元,线程间不可以随意访问和修改,那么该怎么办呢?其实C#提供了跨线程访问的方法,也就是通过委托安全调用从非拥有控件的线程访问控件。

??????? 一、委托

??????? 我们首先先来了解下委托,简单地说,委托就是一个类,它定义了方法传递参数的类型和个数,使得我们可以把方法作为参数进行传递,使得程序具有更好的扩展性。如果大家还不明白的话,我们可以举个例子:

    private   delegate   void setTextDelegate( string msg);   //声明一个委托类型,这个委托类型传递一个string类型参数,并返回为void

    private void setLabelText(string value)    //这是一个传递string类型参数,并返回void的方法
    {
        this.label1.Text = value;
    }

     private void setTextboxTex(string  msg)  //这也是一个传递string类型参数,并返回void的方法
    {
        this.textBox1.Text = msg;
    }

     private void setText(string msg,setTextDelegate std)   //把setTextDelegate类型委托作为一个参数进行传递
     {
         std(msg);     //真正的参数传递
     }

     private void button1_Click(object sender, EventArgs e)
     {

         this.setText("这是label控件", setLabelText);    //因为setLabelText传递的参数类型和返回值都和委托声明的类型一致,所以可以进行传递

         this.setText("这是textBox控件", setTextboxTex);   //同上
     }

??? 总结下,从上面的例子我们可以清楚地看到,只要方法传递的参数类型、个数和返回值与委托声明的一致,我们就可以把该方法作为参数进行传递。

??? 我们也可以将多个方法绑定到委托上,所有绑定上去的方法就会形成一个链表顺序执行。

   private void button1_Click(object sender, EventArgs e)
   {       
       setTextDelegate std = new setTextDelegate(setLabelText);     //实例化委托并绑定setLabelText方法

        std += setTextboxTex;     //使用"+="号绑定方法,解绑使用"-="号

        this.setText("这是控件", std);
    }

?? ?? 这样setTextDelegate绑定了两个方法,当我们this.setText("这是控件", std);时两个方法会顺序执行,这样做大大提高了程序的可扩性。

???? 注意:委托并没有提供0参数的构造函数,所以setTextDelegate std = new setTextDelegate();?? std += setLabelText;???? std += setTextboxTex;??? 这样编译会出错。 ??? 二、线程安全及委托调用

?????? 在讲解跨线程委托调用的方法前,我们先了解几个常用的方法和属性:

?????? 1.Control.InvokeRequired属性(Control是所有控件的基类),这个属性用来判断Control控件是否为调用线程创建的,如果为否的话,也就是创建Control控件的线程不是调用线程,返回false,否则返回true。

????? 2.Control.Invoke() 方法,这是同步调用的方法,它顺序执行Invoke(Delegate)里的委托方法会再继续执行下面的方法,下面会详细解释。

????? 3.Control.BeginInvoke()方法,这是异步调用的方法,它会类似于把委托内的方法又创建了一条线程来执行,只有调用线程进行Sleep切换时才会执行委托方法,下面会详细解释它和Control.Invoke()方法的区别。

????? 先来看个例子:

   private   delegate   void setTextDelegate( int value);   //先声明一个传递int类型参数,并返回为void的委托

    private void button1_Click(object sender, EventArgs e)
   {
      Thread newThread = new Thread(new ThreadStart(threadHandler));
       ?newThread.Start();
   }

    private void threadHandler()
    {

        for(int i =0 ; i <=100 ;  i ++)
        {
             this.UIHandler(i);

             Thread.Sleep(100);

        }
    }

    ?private void UIHandler(int value)
     {

        if(this.label1.InvokeRequired)  //判断label1控件是否是调用线程(即newThread线程)创建的,也就是是否跨线程调用,如果是则返回true,否则返回false
        {
            this.label1.BeginInvoke(new setTextDelegate(setLabelText),new object []{ value});  //异步调用setLabelText方法,并传递一个int参数
        }
        else
        {
            this.label1.Text = value.ToString() + "%";
        }
     }

     private void setLabelText(int value)  //当跨线程调用时,调用该方法进行UI界面更新
     {
        this.label1.Text = value.ToString() + "%";
     }

???? 这是一个简单的跨线程调用的例子,不懂的可以把例子拷贝自己运行下就清楚了,其实原理很简单,分两步就搞定了:

??? 1. 声明一个委托类型,定义它需要传递的参数类型、个数和返回的类型。

??? 2.判断是否跨线程调用更新控件内容(Control.InvokeRequired),如果是的话,就要用Invoke()或BeginInvoke()执行委托。

???? 三、Control.Invoke()与Control.BeginInvoke()方法的区别

???? 我们都知道这两个方法都可以进行委托执行,但其中Control.Invoke()是同步调用执行,Control.BeginInvoke()是异步调用执行。

???? MSDN上的解释是这样的:

???? Control.Invoke()方法:在拥有此控件的基础窗口句柄的线程上执行委托。?

? ? Contr