c#多线程(UI线程,控件显示更新) Invoke和BeginInvoke 区别

若是只是直接使用子线程访问UI控件,直接看内容三,若是想深刻了解从内容一看起。html

 

1、Control.Invoke和BeginInvoke方法的区别数据库

先上总结:安全

Control.Invoke 方法 (Delegate) :拥有此控件的基础窗口句柄的线程上执行指定的委托。但委托的内容在UI线程上执行。网络

Control.BeginInvoke 方法 (Delegate) :在建立控件的基础句柄所在线程上异步执行指定委托。但委托的内容在UI线程上执行。多线程

 

(一)Control的Invoke和BeginInvoke
咱们要基于如下认识:
(1)Control的Invoke和BeginInvoke与Delegate的Invoke和BeginInvoke是不一样的。
(2)Control的Invoke和BeginInvoke的参数为delegate,委托的方法是在Control的线程上执行的,也就是咱们平时所说的UI线程。

咱们以代码(一)来看(Control的Invoke)
private delegate void InvokeDelegate();
private void InvokeMethod(){
   //C代码段
}
private void butInvoke_Click(object sender, EventArgs e) {
   //A代码段.......
   this.Invoke(new InvokeDelegate(InvokeMethod));
   //B代码段......
}
你以为代码的执行顺序是什么呢?记好Control的Invoke和BeginInvoke都执行在主线程即UI线程上
A------>C---------------->B
解释:(1)A在UI线程上执行完后,开始Invoke,Invoke是同步
(2)代码段B并不执行,而是当即在UI线程上执行InvokeMethod方法,即代码段C。
(3)InvokeMethod方法执行完后,代码段C才在UI线程上继续执行。

看看代码(二),Control的BeginInvoke
private delegate void BeginInvokeDelegate();
private void BeginInvokeMethod(){
   //C代码段
}
private void butBeginInvoke_Click(object sender, EventArgs e) {
   //A代码段.......
   this.BeginInvoke(new BeginInvokeDelegate(BeginInvokeMethod));
   //B代码段......
}

你以为代码的执行顺序是什么呢?记好Control的Invoke和BeginInvoke都执行在主线程即UI线程上
A----------->B--------------->C慎重,这个只作参考。。。。。,我也不愿定执行顺序,若是有哪位达人知道的话请告知。
解释::(1)A在UI线程上执行完后,开始BeginInvoke,BeginInvoke是异步
(2)InvokeMethod方法,即代码段C不会执行,而是当即在UI线程上执行代码段B。
(3)代码段B执行完后(就是说butBeginInvoke_Click方法执行完后),InvokeMethod方法,即代码段C才在UI线程上继续执行。

由此,咱们知道:
Control的Invoke和BeginInvoke的委托方法是在主线程,即UI线程上执行的。也就是说若是你的委托方法用来取花费时间长的数据,而后更新界面什么的,千万别在UI线程上调用Control.Invoke和Control.BeginInvoke,由于这些是依然阻塞UI线程的,形成界面的假死。

那么,这个异步究竟是什么意思呢?

异步是指相对于调用BeginInvoke的线程异步,而不是相对于UI线程异步,你在UI线程上调用BeginInvoke ,固然不行了。----摘自"Invoke和BeginInvoke的真正涵义"一文中的评论。
BeginInvoke的原理是将调用的方法Marshal成消息,而后调用Win32 API中的RegisterWindowMessage()向UI窗口发送消息。----摘自"Invoke和BeginInvoke的真正涵义"一文中的评论。

(二)咱们用Thread来调用BeginInvoke和Invoke
      咱们开一个线程,让线程执行一些耗费时间的操做,而后再用Control.Invoke和Control.BeginInvoke回到用户UI线程,执行界面更新。

代码(三)  Thread调用Control的Invoke
private Thread invokeThread;        
private delegate void invokeDelegate();   
private void StartMethod(){
   //C代码段......
   Control.Invoke(new invokeDelegate(invokeMethod)); 
并发

【上面这句话做用是把消息发送给UI线程,而后在这个点阻塞,等UI线程处理完发送的消息后才继续往下执行,特别适合阻塞更新UI控件】异步

  //D代码段......
}
private void invokeMethod(){
  //E代码段
}
private void butInvoke_Click(object sender, EventArgs e) {
   //A代码段.......
   invokeThread = new Thread(new ThreadStart(StartMethod));
ui

   invokeThread.Start();this

   //B代码段......
}

你以为代码的执行顺序是什么呢?记好Control的Invoke和BeginInvoke都执行在主线程即UI线程上
A------>(Start一开始B和StartMethod的C就同时执行)---->(C执行完了,无论B有没有执行完,invokeThread把消息封送(invoke)给UI线程,而后本身等待)---->UI线程处理完butInvoke_Click消息后,处理invokeThread封送过来的消息,执行invokeMethod方法,即代码段E,处理日后UI线程切换到invokeThread线程。
这个Control.Invoke是相对于invokeThread线程同步的,阻止了其运行。

解释:
1。UI执行A
2。UI开线程InvokeThread,B和C同时执行,B执行在线程UI上,C执行在线程invokeThread上。
3。invokeThread封送消息给UI,而后本身等待,UI处理完消息后,处理invokeThread封送的消息,即代码段E
4。UI执行完E后,转到线程invokeThread上,invokeThread线程执行代码段D

代码(四)  Thread调用Control的BeginInvoke
private Thread beginInvokeThread;
private delegate void beginInvokeDelegate();
private void StartMethod(){
   //C代码段......
   Control.BeginInvoke(new beginInvokeDelegate(beginInvokeMethod));spa

【上面这句话做用是把消息发送给UI线程,而后不阻塞继续往下执行D代码,也就是再也不管发送的消息了,UI线程处理完本身的事情后会处理这个发送的消息,通常不多用这种方法更新UI控件,由于通常状况下都要求阻塞次(子)线程更新的UI控件的,只要不阻塞UI主(父)线程,让用户感受不到界面死机就行。】

  //D代码段......
}
private void beginInvokeMethod(){
  //E代码段
}
private void butBeginInvoke_Click(object sender, EventArgs e) {
   //A代码段.......
   beginInvokeThread = new Thread(new ThreadStart(StartMethod));
   beginInvokeThread .Start();
   //B代码段......
}
你以为代码的执行顺序是什么呢?记好Control的Invoke和BeginInvoke都执行在主线程即UI线程上
A在UI线程上执行----->beginInvokeThread线程开始执行,UI继续执行代码段B,并发地invokeThread执行代码段C-------------->无论UI有没有执行完代码段B,这时beginInvokeThread线程把消息封送给UI,单本身并不等待,继续向下执行-------->UI处理完butBeginInvoke_Click消息后,处理beginInvokeThread线程封送过来的消息。


解释:
1。UI执行A
2。UI开线程beginInvokeThread,B和C同时执行,B执行在线程UI上,C执行在线程beginInvokeThread上。
3。beginInvokeThread封送消息给UI,而后本身继续执行代码D,UI处理完消息后,处理invokeThread封送的消息,即代码段E
有点疑问:若是UI先执行完毕,是否是有可能过了段时间beginInvokeThread才把消息封送给UI,而后UI才继续执行封送的消息E。如图浅绿的部分。


Control的BeginInvoke是相对于调用它的线程,即beginInvokeThread相对是异步的。
所以,咱们能够想到。若是要异步取耗费长时间的数据,好比从数据库中读大量数据,咱们应该这么作。
(1)若是你想阻止调用线程,那么调用代码(三),代码段D删掉,C改成耗费长时间的操做,由于这个操做是在另一个线程中作的。代码段E改成更新界面的方法。
(2)若是你不想阻止调用线程,那么调用代码(四),代码段D删掉,C改成耗费长时间的操做,由于这个操做是在另一个线程中作的。代码段E改成更新界面的方法。

文章转自:http://www.cnblogs.com/mashang/archive/2009/08/01/1536730.html

 

 

2、有了一点上面的知识,来看“C#多线程异步访问winform中控件(为了加深对上面的理解,但还不是最简单的方法,最简单的方法见下面)”:

转自http://blog.csdn.net/ajrm0925/article/details/5314099

咱们在作winform应用的时候,大部分状况下都会碰到使用多线程控制界面上控件信息的问题。然而咱们并不能用传统方法来作这个问题,下面我将详细的介绍。

      首先来看传统方法:

     public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            Thread thread = new Thread(ThreadFuntion);
            thread.IsBackground = true;
            thread.Start();
        }
        private void ThreadFuntion()
        {
            while (true)
            {
                this.textBox1.Text = DateTime.Now.ToString();
                Thread.Sleep(1000);
            }
        }
    }

       运行这段代码,咱们会看到系统抛出一个异常:Cross-thread operation not valid:Control 'textBox1' accessed from a thread other than the thread it was created on. 这是由于.net 2.0之后增强了安全机制,不容许在winform中直接跨线程访问控件的属性。那么怎么解决这个问题呢,下面提供几种方案。

第一种方案,咱们在Form1_Load()方法中加一句代码:

      private void Form1_Load(object sender, EventArgs e)
       {
            Control.CheckForIllegalCrossThreadCalls = false;
            Thread thread = new Thread(ThreadFuntion);
            thread.IsBackground = true;
            thread.Start();
        }
      加入这句代码之后发现程序能够正常运行了。这句代码就是说在这个类中咱们不检查跨线程的调用是否合法(若是没有加这句话运行也没有异常,那么说明系统以及默认的采用了不检查的方式)。然而,这种方法不可取。咱们查看CheckForIllegalCrossThreadCalls 这个属性的定义,就会发现它是一个static的,也就是说不管咱们在项目的什么地方修改了这个值,他就会在全局起做用。并且像这种跨线程访问是否存在异常,咱们一般都会去检查。若是项目中其余人修改了这个属性,那么咱们的方案就失败了,咱们要采起另外的方案。

第二种方案,就是使用delegate和invoke来从其余线程中控制控件信息。网上有不少人写了这种控制方式,然而我看了不少这种帖子,代表上看来是没有什么问题的,可是实际上并无解决这个问题,首先来看网络上的那种不完善的方式:

public partial class Form1 : Form
    {
        private delegate void FlushClient();//代理
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            Thread thread = new Thread(CrossThreadFlush);

             thread.IsBackground=true;
             thread.Start();
        }

        private void CrossThreadFlush()
        {
            //将代理绑定到方法 
            FlushClient fc = new FlushClient(ThreadFuntion);
            this.BeginInvoke(fc);//调用代理 【注意this只Form窗体,因此也是一个Control,具备BeginInvoke方法,而且在UI线程中执行代理fc】
        }
        private void ThreadFuntion()
        {
            while (true)
            {
                this.textBox1.Text = DateTime.Now.ToString();
                Thread.Sleep(1000);
            }
        }
    }

       使用这种方式咱们能够看到跨线程访问的异常没有了。可是新问题出现了,界面没有响应了。为何会出现这个问题,咱们只是让新开的线程无限循环刷新,理论上应该不会对主线程产生影响的。其实否则,这种方式其实至关于把这个新开的线程“注入”到了主控制线程中,它取得了主线程的控制。只要这个线程不返回,那么主线程将永远都没法响应。就算新开的线程中不使用无限循环,使能够返回了。这种方式的使用多线程也失去了它原本的意义。    

第三种方案,如今来让咱们看看推荐的解决方案:

public partial class Form1 : Form
    {
        private delegate void FlushClient();//代理
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            Thread thread = new Thread(CrossThreadFlush);
            thread.IsBackground = true;
            thread.Start();
        }

        private void CrossThreadFlush()
        {
            while (true)
            {
                //将sleep和无限循环放在等待异步的外面
                Thread.Sleep(1000);
                ThreadFunction();
            }
        }
        private void ThreadFunction()
        {
            if (this.textBox1.InvokeRequired)//等待异步
            {
                FlushClient fc = new FlushClient(ThreadFunction);
                this.Invoke(fc);//经过代理调用刷新方法 【注意this只Form窗体,因此也是一个Control,具备BeginInvoke方法,而且在UI线程中执行代理fc】
            }
            else
            {
                this.textBox1.Text = DateTime.Now.ToString();
            }
        }
    }

       运行上述代码,咱们能够看到问题已经被解决了,经过等待异步,咱们就不会老是持有主线程的控制,这样就能够在不发生跨线程调用异常的状况下完成多线程对winform多线程控件的控制了。

 

 

3、最简单的C#多线程异步访问winform中控件的方法,其实就是对上面“推荐的方案进行总结和简化的来的”

转自:http://blog.csdn.net/ajrm0925/article/details/5314195

private void button1_Click(object sender, EventArgs e)
{
    ThreadStart ts = new ThreadStart(add);
    Thread th = new Thread(ts);
    th.Start(); 【启动子线程add】
 }

delegate void DelChangText(string ss);  //【先声明一个代理,是一个类型】

void add()  【这是在子线程中执行的内容】
{
   int a = 1;
   int b = 2;
   string sum = Convert.ToString(a + b);
   // 计算完成须要在一个文本框里显示
   this.BeginInvoke(new DelChangText(intoText), sum);  //【从add子线程转到UI主线程执行intoText方法,这里也可使用this.Invoke,至于区别见大标题一】

   //或改成intoText(sum);执行结果也正确
}

void intoText(string sum)                     //【再写对控件进行操做的方法,注意它的通常固定写法】
{
   if (this.InvokeRequired)  //借助this即Form对象来判断正在执行的代码是否是在UI线程中,若是是在UI线程,则说明没在异步调用,因此条件为假
   {
      this.BeginInvoke(new DelChangText(intoText), sum);  //【this是Form控件,因此changText会在UI线程中执行,不懂的看大标题一和二】

   }
   else
   {
      textBox1.Text = sum;
   }
}

 解释:从上面代码来看if (this.InvokeRequired)彷佛是多余的由于子线程add中使用了this.BeginInvoke转到UI线程中执行inntoText方法,因此if (this.InvokeRequired)条件判断永远为假,但这样写安全,谁能保证子线程中开启的inntoText方法在UI线程中执行呢。

其实能够把上面add方法中的this.BeginInvoke改成直接执行intoText试试看,是否是进入到if语句中了。此时执行也是正确的。

相关文章
相关标签/搜索