主页 > 编程资料 > C# >
发布时间:2015-01-17 作者:网络 阅读:337次

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

        一、委托

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

Csharp代码  收藏代码
  1. private   delegate   void setTextDelegate( string msg);   //声明一个委托类型,这个委托类型传递一个string类型参数,并返回为void  
  2.   
  3. private void setLabelText(string value)    //这是一个传递string类型参数,并返回void的方法  
  4. {  
  5.     this.label1.Text = value;  
  6. }  
  7.   
  8.  private void setTextboxTex(string  msg)  //这也是一个传递string类型参数,并返回void的方法  
  9. {  
  10.     this.textBox1.Text = msg;  
  11. }  
  12.   
  13.  private void setText(string msg,setTextDelegate std)   //把setTextDelegate类型委托作为一个参数进行传递  
  14.  {  
  15.      std(msg);     //真正的参数传递  
  16.  }  
  17.   
  18.  private void button1_Click(object sender, EventArgs e)  
  19.  {  
  20.   
  21.      this.setText("这是label控件", setLabelText);    //因为setLabelText传递的参数类型和返回值都和委托声明的类型一致,所以可以进行传递  
  22.   
  23.      this.setText("这是textBox控件", setTextboxTex);   //同上  
  24.  }  

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

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

Csharp代码  收藏代码
  1. private void button1_Click(object sender, EventArgs e)  
  2. {         
  3.     setTextDelegate std = new setTextDelegate(setLabelText);     //实例化委托并绑定setLabelText方法  
  4.   
  5.      std += setTextboxTex;     //使用"+="号绑定方法,解绑使用"-="号  
  6.   
  7.      this.setText("这是控件", std);  
  8.  }  

 

      这样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()方法的区别。

      先来看个例子:

Csharp代码  收藏代码
  1. private   delegate   void setTextDelegate( int value);   //先声明一个传递int类型参数,并返回为void的委托  
  2.   
  3.  private void button1_Click(object sender, EventArgs e)  
  4. {  
  5.    Thread newThread = new Thread(new ThreadStart(threadHandler));  
  6.      newThread.Start();  
  7. }  
  8.   
  9.  private void threadHandler()  
  10.  {  
  11.   
  12.      for(int i =0 ; i <=100 ;  i ++)  
  13.      {  
  14.           this.UIHandler(i);  
  15.   
  16.           Thread.Sleep(100);  
  17.   
  18.      }  
  19.  }  
  20.   
  21.   private void UIHandler(int value)  
  22.   {  
  23.   
  24.      if(this.label1.InvokeRequired)  //判断label1控件是否是调用线程(即newThread线程)创建的,也就是是否跨线程调用,如果是则返回true,否则返回false  
  25.      {  
  26.          this.label1.BeginInvoke(new setTextDelegate(setLabelText),new object []{ value});  //异步调用setLabelText方法,并传递一个int参数  
  27.      }  
  28.      else  
  29.      {  
  30.          this.label1.Text = value.ToString() + "%";  
  31.      }  
  32.   }  
  33.   
  34.   private void setLabelText(int value)  //当跨线程调用时,调用该方法进行UI界面更新  
  35.   {  
  36.      this.label1.Text = value.ToString() + "%";  
  37.   }  

 

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

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

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

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

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

     MSDN上的解释是这样的:

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

    Control.BeginInvoke()方法:在创建控件的基础句柄所在线程上异步执行委托。 

     从MSDN的解释我们可以得出,委托都是在Control线程执行的,也就是UI主线程执行的。两者的区别主要是异步执行和顺序执行的区别,也就是执行顺序的不一致。

    下面举个例子来看就明白了,就拿上面的例子来讲解好了。

Csharp代码  收藏代码
  1.  private   delegate   void setTextDelegate( int value);   //先声明一个传递int类型参数,并返回为void的委托  
  2.   
  3.  private void button1_Click(object sender, EventArgs e)  
  4. {  
  5.    Thread newThread = new Thread(new ThreadStart(threadHandler));  
  6.   
  7.      newThread.Start();  
  8. }  
  9.   
  10.  private void threadHandler()  
  11.  {  
  12.      for(int i =0 ; i <=100 ;  i ++)  
  13.      {  
  14.          this.UIHandler(i);      
  15.   
  16.           Thread.Sleep(100);  
  17.       }  
  18.   
  19.  }  
  20.  private void UIHandler(int value)  
  21.  {  
  22.        //  ........①  
  23.        if(this.label1.InvokeRequired)  //判断label1控件是否是调用线程(即newThread线程)创建的,也就是是否跨线程调用,如果是则返回true,否则返回false  
  24.        {  
  25.            this.label1.Invoke(new setTextDelegate(setLabelText),new object []{ value});   //同步调用......②  
  26.   
  27.            //this.label1.BeginInvoke(new setTextDelegate(setLabelText),new object []{ value});   //异步调用 ......③  
  28.         }  
  29.         else  
  30.         {  
  31.             this.label1.Text = value.ToString() + "%";   //.......④  
  32.         }  
  33.         //.........⑤  
  34.   }  
  35.   
  36.   private void setLabelText(int value)  //当跨线程调用时,调用该方法进行UI界面更新  
  37.   {  
  38.       this.label1.Text = value.ToString() + "%";     //..........⑥  
  39.   }  
       大家先猜猜看,如果是Invoke调用的话,也就是(①、②、④、⑤、⑥)的执行顺序,而如果是BeginInvoke调用的话,也就是(①、③、④、⑤、⑥)的执行顺序。

 

     正确的执行顺序:Invoke是①->②->⑥-->⑤,而BeginInvoke是①->③->⑤->⑥,④这步不会执行到。

     大家可以自己去试下,其实原理我上面已经说过了,使用Invoke方法的话,调用线程会马上进行切换操作,切换到Control控件所在线程(UI线程)进行界面的更新后,再切换回来继续执行下面的内容,而BeginInvoke方法的话,调用线程不会马上进行切换操作,它会在Thread.Sleep(100);的时候进行切换到UI线程进行界面的更新,所以用BeginInvoke方法的时候一定要注意,每次执行完要Thread.Sleep(100);下让界面更新,否则界面还是不会有变化的

     四、委托的类型

     .Net Framework 提供了两种类型的委托,一种是自定义型的,一种是系统定义型的(固定类型)。自定义型的就不说了,也就是需要自己进行声明委托类型,灵活性高,不过系统默认提供的几个固定类型的委托也可以让我们快速实现委托。

    1.   public delegate void  MethodInvoker ()   //声明返回值为 void 且不接受任何参数传递的任何方法    

Csharp代码  收藏代码
  1. private void button1_Click(object sender, EventArgs e)  
  2.   
  3.   Thread newThread = new Thread(new ThreadStart(threadHandler));            
  4.     newThread.Start();  
  5. }  
  6.   
  7.  private void threadHandler()  
  8.  {  
  9.      if(this.label1.InvokeRequired)  //判断label1控件是否是调用线程(即newThread线程)创建的,也就是是否跨线程调用,如果是则返回true,否则返回false  
  10.      {  
  11.          this.label1.Invoke(new MethodInvoker(threadHandler));   //把封包发送UI线程,即UI线程执行threadHandler方法  
  12.      }  
  13.      else  
  14.      {  
  15.          this.label1.Text = "这是lable控件";   //第二次this.label1.InvokeRequired判断后会执行到这里  
  16.      }  
  17.  }  
    2. public delegate void EventHandler ( Object senderEventArgs e)  //表示将处理不包含事件数据的事件的方法。 

 

Csharp代码  收藏代码
  1. private void button1_Click(object sender, EventArgs e)  
  2.   
  3.   Thread newThread = new Thread(new ThreadStart(threadHandler));            
  4.   
  5.      newThread.Start();  
  6. }  
  7.   
  8.  private void threadHandler()  
  9.  {  
  10.        if(this.label1.InvokeRequired)  //判断label1控件是否是调用线程(即newThread线程)创建的,也就是是否跨线程调用,如果是则返回true,否则返回false  
  11.        {  
  12.            this.label1.Invoke(new EventHandler(setLabelText),new object[]{"这是lable控件"});   //EventHandler可以传递object参数  
  13.        }  
  14.        else  
  15.        {  
  16.            this.label1.Text = "这是lable控件";  
  17.        }  
  18.   }  
  19.   
  20.   private void setLabelText(object o, System.EventArgs e)  
  21.    {  
  22.        this.label1.Text =  o.ToString();  
  23.    }  
            后述:委托的内容就讲到这吧,讲得不对不好的地方请大家多多包涵。 

关键字词: