线程实用解析(六)Control.Invoke()和Control.BeginInvoke()

在以前的章节中,我们不只一次的提到过,不能在非创建UI控件的线程中操作UI元素,否则会和UI控件创建线程(一般是主线程)产生冲突,造成不可预料的后果。

该如何解决这个问题呢?除了上一节所讲的BackgroundWorkerTimer以外,微软将Control类实现了ISynchronizeInvoke接口,提供了InvokeBeginInvoke方法来提供让其它线程更新GUI界面控件的机制

下边还是通过一个例子给大家讲解一下Control.Invoke()Control.BeginInvoke();

首先新建一个WinForm应用程序,在Form窗体上做如下布局:

然后,新建一个委托

public delegate void ShowTime();

ControlInvoke()按钮的click事件里添加如下代码:

Button_click(object sender ,EventArgs e)

{

    ShowTime showTime = new ShowTime(Time);

            this.Invoke(showTime);//Invoke()方法需要一个委托参数,也就是要执行委托方法

            //在执行委托之后弹出对话框,显示当前的线程ID,是否为后台线程

            MessageBox.Show("当前线程ID为:" + Thread.CurrentThread.ManagedThreadId + "是否为后台线程?" + Thread.CurrentThread.IsBackground);

}

//委托调用的方法

private  void Time()

        {

            this.button10.Text = DateTime.Now.ToString();//Button.Text属性设置为当前的时间

            for (int i = 0; i <= 100; i++)

            {

                sum += i;

            }

            MessageBox.Show("Sum值为:" + sum + "当前线程ID为:" + Thread.CurrentThread.ManagedThreadId + "是否为后台线程?" + Thread.CurrentThread.IsBackground);//显示当前的线程ID,是否为后台线程

        }

实验结果如下:依次是

1、

 

2、

 

3、

 

由实验结果我们可以得出以下结论:Control.Invoke()方法所执行的委托方法跟创建UI元素的线程是一个线程,也就是主线程。Control.Invoke()虽然执行了委托方法,但是并没有创建新的线程,而且从实验结果出现的先后顺序可以得知,Invoke()是同步独占式的执行,只有在Invoke()方法执行完以后才执行Button-Click()方法里的MessageBox.Show()方法。

下面在看下Control.BeginInvoke()

首先新建一个委托:

public delegate void ShowTime(int a);

然后在Button_click()方法中添加如下代码:

private void button11_Click(object sender, EventArgs e)

        {

            ShowTime showTime = new ShowTime(Time);//新建一个委托实例

            this.BeginInvoke(showTime, 100);//调用Control.BeginInvoke()方法,第一个参数是一个委托实例,第二参数是委托调用函数所需要的参数

            MessageBox.Show("当前线程ID为:" + Thread.CurrentThread.ManagedThreadId + "是否为后台线程?" + Thread.CurrentThread.IsBackground);//显示当前的线程ID

        }

//委托调用函数

private  void Time(int a)

        {

            this.button11.Text = DateTime.Now.ToString();//改变ButtonText属性为当前的时间

            for (int i = 0; i <= a; i++)

            {

                sum += i;

            }

            Thread.Sleep(3000);

            MessageBox.Show("Sum值为:" + sum + "当前线程ID为:" + Thread.CurrentThread.ManagedThreadId + "是否为后台线程?" + Thread.CurrentThread.IsBackground);//显示当前的线程的ID

        }

实验结果如下:依次是:

1

 

2

 

3

 

由实验结果我们可以得出以下结论:Control.BeginInvoke()方法所执行的委托方法跟创建UI元素的线程是一个线程,也就是主线程。Control.BeginInvoke()虽然执行了委托方法,但是并没有创建新的线程,而且从实验结果出现的先后顺序可以得知,BeginInvoke()在这里也是同步执行的,只有在BeginInvoke()方法执行完以后才执行Button-Click()方法里的MessageBox.Show()方法。之前有看网上、园子里说Control.BeginInvoke()是异步执行的这个本没有错。但是所谓的异步在本例中相当于执行this.BeginInvoke(showTime100);语句之后,应该马上执行下边一句MessageBox.Show();最后再执行委托方法,为了对此进行测试,我在Time()函数里将线程休眠了3秒钟,但是事实证明,只有在Time()方法执行完之后,才会执行MessageBox.show()语句,才会出现上边所示的结果,所谓的异步执行,在这里并没有得到体现,这是为什么呢?让我们继续往下看。

由以上的例子可知ControlInvokeBeginInvoke的委托方法是在主线程,即UI线程上执行的。如果委托方法不会花费很长的时间,那直接用Control.Invoke()Control.BeginInvoke()方法,如果你的委托方法用来取花费时间长的数据,然后更新界面什么的,千万别在UI线程上调用Control.InvokeControl.BeginInvoke,因为这些是依然阻塞UI线程的,造成界面的假死,那么我们该该如何办呢?最好的解决办法就是重新建立一个线程,然后在线程调用函数里再启用Control.Invoke()或者Control.BeginInvoke()

下面还是给大家做一个示例,然后在示例中慢慢讲解。

Form窗体里放置2Button如图

 

先创建一个委托 private delegate void Showing();

然后在Thread调用Control.Invoke()按钮的Click方法中添加如下代码:

private void button4_Click(object sender, EventArgs e)

        {

            //显示当前线程ID

            MessageBox.Show("A,我最先执行!"+Thread.CurrentThread.ManagedThreadId);

         //创建一个新的线程,并在线程调用函数ShowThread中,调用Control.Invoke()方法

      Thread thread = new Thread(new ThreadStart(ShowThread));

            thread.Start();

            MessageBox.Show("B,我和C同步执行!" + Thread.CurrentThread.ManagedThreadId);

        }

//线程调用方法,在这个方法里再调用this.Invoke()方法操作UI元素

void ShowThread()

        {

            MessageBox.Show("C,Thread调用Control.Invoke()方法开始!");

            this.Invoke(new Showing(ShowResult));

            MessageBox.Show("E,我最后执行!,Thread调用Contorol.Invoke()方法结束!");

        }

        //委托Showing调用的函数

        private static  void ShowResult()

        {

            int sum = 0;

            for (int i = 0; i < 1000000000; i++)

            {

                sum += i;

            }

            MessageBox.Show("D:占用UI线程。----"+Thread.CurrentThread.ManagedThreadId);//显示当前线程所在ID

        }

实验结果如下:

依次为1

 

2、

 

3、

 

4、

 

由实验结果可知,在线程thread调用的ShowThread方法里,启动了Control.Invoke()方法,在Control.Invoke()方法调用的委托方法ShowResult()完全执行完之后,才执行ShowThread()里的MessageBox.Show("E,我最后执行!,Thread调用Contorol.Invoke()方法结束!");由此可见Control.Invoke()方法是同步的。

下面把Control.BeginInvoke()方法也放在一个新的线程调用函数中执行,代码基本上不用怎么变,只需要把Thread调用Control.Invoke()按钮的Click方法里的代码复制到Thread调用Control.BeginInvoke()按钮的Clicked方法里就行,然后把void ShowThread()的代码做如下改动:

        {

            MessageBox.Show("C,Thread调用Control.Invoke()方法开始!");

            this.BeginInvoke(new Showing(ShowResult));

            MessageBox.Show("E,我最后执行!,Thread调用Contorol.Invoke()方法结束!");

        }

运行结果如下:1

 

2、

 

3、

 

4、

 

由实验结果可知:在线程thread调用的ShowThread方法里,启动了Control.BeginInvoke()方法,Control.BeginInvoke()方法调用的委托方法ShowResult()之后,马上就执行了ShowThread()里的MessageBox.Show("E,我最后执行!,Thread调用Contorol.Invoke()方法结束!"),并没有等待委托方法ShowResult()执行完,由于ShowResult()执行时间较长,故最后才显示消息框4,所以说这里的Control.BeginInvoke()是异步操作。这样就可以与Control.Invoke()是同步操作明显的区分开了。

说到BeginInvoke()Invoke()还有朋友问DelegateBeginInvoke()Invoke()Contorl.BeginInvoke()Invoke()的区别,之前的章节中有介绍过DelegateBeginInvoke(),委托的BeginInvoke()主要用于异步操作,其本质是在线程池里又启用了一个新的后台线程,而Contorl.BeginInvoke()它本身就是在创建控件的线程里(也就是UI)线程里执行的,它主要依附于具体的UI元素,可以直接的访问UI元素,但是通过委托的BeginInvoke()是绝对不能访问UI元素的,这点已经强调了很多次了。

 

好了,关于《线程实用详解》这一系列的文章到这里已经结束了,通过学习、总结,自己也收获了很多的东西,当然,还有有些地方总结的还不够全面,还需要继续的努力。学习就是这样一个过程,通过不断的练习、反思、总结然后再反思、练习、总结、升华。希望能给大家带来些帮助,也还请大家不吝赐教,共同交流探讨。

原文地址:https://www.cnblogs.com/Olive116/p/2713098.html