• 线程实用解析(六)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元素的,这点已经强调了很多次了。

     

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

  • 相关阅读:
    JavaScript 以POST方式打开新页面
    C# 实现守护进程
    SQL Server 之 使用RowCount遍历表数据
    SQL Server 之 存储过程调用C#编写的dll文件
    C# 多线程学习整理
    java 学习的有用链接
    git 操作命令
    关于各种Map的那些事
    JAVA 反射机制详解
    深入理解java:注解(Anotation)自定义注解
  • 原文地址:https://www.cnblogs.com/Olive116/p/2713098.html
Copyright © 2020-2023  润新知