浅谈Invoke 和 BegionInvoke的用法

很多人对Invoke和BeginInvoke理解不深刻,不知道该怎么应用,在这篇博文里将详细阐述Invoke和BeginInvoke的用法:

首先说下Invoke和BeginInvoke有两种用法:

1.Control中Invoke,BeginInvoke

2.Delegate中Invokke,BeginInvoke

这两种情况是不同的,我们首先讲一下第一种,也就是Control类中的Invoke,BeginInvoke的用法。我们先来看一下MSDN是如何解释的:

control.invoke(参数delegate)方法:在拥有此控件的基础窗口句柄的线程上执行指定的委托。

control.begininvoke(参数delegate)方法:在创建控件的基础句柄所在线程上异步执行指定委托。

通过官方的解释,我们大概觉得 Invoke是同步的,而BeginInvoke是异步的,至于两者的实际差别,我们通过代码来展示:

  Thread invokeThread;
        public delegate void invokeDelegate();
        private void button1_Click(object sender, EventArgs e)
        {
            //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "AAA");
            Console.WriteLine("AAA");
            invokeThread = new Thread(new ThreadStart(StartMethod));
            invokeThread.Start();
            string a = string.Empty;
            for (int i = 0; i < 3; i++)      //调整循环次数,看的会更清楚
            {
                Thread.Sleep(1000);
                a = a + "B";
            }
           // MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + a);

            Console.WriteLine(a);
        }
        private void StartMethod()
        {
           // MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "CCC");
            Console.WriteLine("CCC");
            button1.Invoke(new invokeDelegate(invokeMethod));
            //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "DDD");
            Console.WriteLine("DDD");
            Console.Read();
        }

        private void invokeMethod()
        {
            //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "EEE");

            Console.WriteLine("EEE");
        }

运行结果为:

从结果可以看出执行时首先执行AAA,然后CCC和BBB同时执行,Invoke将Invoke方法提交给主线程,主线程执行完运行在Invoke上的方法,然后才执行子线程的方法,简单讲同步就是说必须等带Invoke上的方法执行完,才能执行子线程的方法。

再来看看BeginInvokke的执行逻辑:

   Thread invokeThread;
        public delegate void invokeDelegate();
        private void button1_Click(object sender, EventArgs e)
        {
            //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "AAA");
            Console.WriteLine("AAA");
            invokeThread = new Thread(new ThreadStart(StartMethod));
            invokeThread.Start();
            string a = string.Empty;
            for (int i = 0; i < 3; i++)      //调整循环次数,看的会更清楚
            {
                Thread.Sleep(1000);
                a = a + "B";
            }
           // MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + a);

            Console.WriteLine(a);
        }
        private void StartMethod()
        {
           // MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "CCC");
            Console.WriteLine("CCC");
            button1.BeginInvoke(new invokeDelegate(invokeMethod));
            //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "DDD");
            Console.WriteLine("DDD");
            Console.Read();
        }

        private void invokeMethod()
        {
            //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "EEE");

            Console.WriteLine("EEE");
        }

运行的结果为:

从结果可以看出,首先执行AAA,然后CCC和BBB同时执行,由于采用了BeginInvoke方法,即所谓的异步执行,子线程不再等待主线程执行完成,所以DDD输出,主线程执行完BBB再执行EEE.

从运行的结果来看不管时Invoke提交的方法,还是BeginInvoke提交的方法,都是在主线上执行的,所以同步和异步的概念是相对于子线程来说的,在invoke例子中我们会发现invoke所提交的委托方法执行完成后,才能继续执行 DDD;在begininvoke例子中我们会发现begininvoke所提交的委托方法后,子线程讲继续执行DDD,不需要等待委托方法的完成。 那么现在我们在回想下invoke(同步)和begininvoke(异步)的概念,其实它们所说的意思是相对于子线程而言的,其实对于控件的调用总是由 主线程来执行的。我们很多人搞不清这个同步和异步,主要还是因为我们把参照物选错了。其实有时候光看概念是很容易理解错误的,Invoke会阻塞主支线程,BeginInvoke只会阻塞主线程,不会阻塞支线程!因此BeginInvoke的异步执行是指相对于支线程异步而不是相对于主线程异步 。现在是不是彻底搞清楚两者的区别啦。

解决不是从创建控件的线程访问它

在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,Invoke 和 BeginInvoke 就是为了解决这个问题而出现的,使你在多线程中安全的更新界面显示。

正确的做法是将工作线程中涉及更新界面的代码封装为一个方法,通过 Invoke 或者 BeginInvoke 去调用,两者的区别就是一个导致工作线程等待,而另外一个则不会。

由于历史原因,形成了以下几种写法:

            //第一种
            if (this.textBox1.InvokeRequired)
            {
                this.textBox1.Invoke(new EventHandler( delegate { this.textBox1.Text = "测试"; }));
            }

            //第二种
            this.Invoke(new EventHandler(delegate { this.textBox1.Text = "测试"; }));

            //第三种
            this.Invoke(new Action(() => { this.textBox1.Text = "测试"; }));

毫无疑问,第三种调用方法是最佳选择。

delegate中的Invoke方法和BeginInvoke方法

delegate中Invoke方法和BenginInvoke方法主要是用在同步调用,异步调用,异步回调上

A.同步调用,举个简单的例子:

  private void button2_Click(object sender, EventArgs e)
        {
            Add d = new Add((a, b) =>
            {
                Thread.Sleep(5000);
                return a + b;
            }); //实例化委托并给委托赋值

            int result = d.Invoke(2, 5);

            Console.WriteLine("继续做别的事情");

            Console.WriteLine(result.ToString());
            Console.Read();

        }

        public delegate int Add(int i1,int i2);

Invoke方法的参数很简单,一个委托,一个参数表(可选),而Invoke方法的主要功能就是帮助你在UI线程上调用委托所指定的方法。Invoke方法首先检查发出调用的线程(即当前线程)是不是UI线程,如果是,直接执行委托指向的方法,如果不是,它将切换到UI线程,然后执行委托指向的方法。不管当前线程是不是UI线程,Invoke都阻塞直到委托指向的方法执行完毕,然后切换回发出调用的线程(如果需要的话),返回。
所以Invoke方法的参数和返回值和调用他的委托应该是一致的

B.异步调用

举个简单的例子:

  public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //委托类型的Begininvoke(<输入和输出变量>,AsyncCallbac callback,object asyncState)方法:异步调用的核心
            //第一个参数10,表示委托对应的方法实参。
            //第二个参数callback,回调函数,表示异步调用结束时自动调用的函数。
            //第三个参数asyncState,用于向回调函数提供相关参数信息
            //返回值:IAsyncResult -->异步操作状态接口,封装了异步执行的中的参数
            //IAsyncResult接口成员:查看帮助文档
            Calcute objCalcute = new Calcute(ExcuteDelegate1);

            IAsyncResult result = objCalcute.BeginInvoke(1, 2, null, null); //IAsyncResult是一个接口

            this.textBox1.Text = "等待结果.......";

            this.textBox2.Text = ExcuteDelegate2(20, 10).ToString();

//委托类型的EndInvoke()方法:借助于IAsyncResult接口对象,不断查询异步调用是否结束。
             //该方法知道被异步调用的方法所有参数,所有,异步调用完毕后,取出异步调用结果作为返回值。

this.textBox1.Text = objCalcute.EndInvoke(result).ToString();

        }

        public delegate int Calcute(int i1, int i2);

        int ExcuteDelegate1(int i1, int i2)
        {
            Thread.Sleep(5000);
            return i1 + i2;
        }

        int ExcuteDelegate2(int i1, int i2)
        {
            return i1 * i2;
        }
    }

从运行结果可以看出运行BeginInvoke上运行的方法时不会影响 this.textBox2.Text = ExcuteDelegate2(20, 10).ToString(); 的执行,但是EndInvoke方法需要等待线程执行完毕,所以依旧会阻塞主线程的执行,为了解决这个问题,异步回调就出现了,其实可以将异步执行看成是异步回调的特殊情况

C.异步回调

看下面的例子:

 public FrmCalllBack()
        {
            InitializeComponent();

            //【3】初始化委托变量
            this.objMyCal = new MyCalculator(ExecuteTask);

            //也可以直接使用Lambda表达式
            this.objMyCal = (num, ms) =>
                {
                    System.Threading.Thread.Sleep(ms);
                    return num * num;
                };
        }
        //【3】创建委托变量(因为异步函数和回调函数都要用,所以定义成员变量)
        private MyCalculator objMyCal = null;

        //【1】声明一个委托
        public delegate int MyCalculator(int num, int ms);

        /// <summary>
        /// 【2】根据委托定义一个方法:返回一个数的平方
        /// </summary>
        /// <param name="num">基数</param>
        /// <param name="ms">延迟的时间:秒</param>
        /// <returns></returns>
        private int ExecuteTask(int num, int ms)
        {
            System.Threading.Thread.Sleep(ms);
            return num * num;
        }

        //【4】同时执行多个任务
        private void btnExec_Click(object sender, EventArgs e)
        {
            for (int i = 1; i < 11; i++)//产生10个任务
            {
                //开始异步执行,并封装回调函数 objMyCal.BeginInvoke(10 * i, 1000 * i, null, i);
                objMyCal.BeginInvoke(10 * i, 1000 * i, MyCallBack, i);
                //最后一个参数 i 给回调函数的字段AsyncState赋值,如果数据很多可以定义成类或结构
            }
        }
        //【5】回调函数
        private void MyCallBack(IAsyncResult result)
        {
            int res = objMyCal.EndInvoke(result);

            //异步显示结果:result.AsyncState字段用来封装回调时自定义的参数,object类型
            Console.WriteLine("第{0}个计算结果为:{1}", result.AsyncState.ToString(), res);
        }
    }

从运行结果看出,异步回调不再阻塞主线程的执行。注意: BeginInvoke和EndInvoke必须成对调用.即使不需要返回值,但EndInvoke还是必须调用,否则可能会造成内存泄漏。

异步编程的总结:

1. 异步编程是建立在委托基础上的一种编程方法。
2. 异步调用的每个方法都是在独立的线程中执行。因此,本质上就是一种多线程程序,也可以说是一种“简化版本”的多线程技术。
3. 比较适合在后台运行较为耗费时间的"简单任务",并且任务要求相互独立,任务中不应该有代码直接访问可视化控件。
4. 如果后台任务要求必须按照特定顺序执行,或者必须访问共享资源,则异步编程不适合,而应该直接采用多线程开发技术。

原文地址:https://www.cnblogs.com/Artist007/p/11045064.html

时间: 2024-08-02 06:36:34

浅谈Invoke 和 BegionInvoke的用法的相关文章

VC++ 浅谈VS2010中CMFCToolBar的用法

本文将给大家介绍Visual Studio 2010中CMFCToolBar的用法,CMFCToolBar可以让用户自定义工具栏图标,使用静态成员函数SetUserImages()将一个CMFCToolBarImages对象设置进去,由所有CMFCToolBar对象共享. AD: 自从VS2008中增加了一些特性的菜单,但这些特性在帮助中说明的很少,给使用者造成了很多麻烦.笔者经过搜索以及自己的摸索,对其的用法有了初步了解,形成本文,如果能够为后来者解决一些问题,笔者将会感到欣慰. 一.向导自动

由jtable浅谈vector&lt;vector&lt;Object&gt;&gt;的用法(转自a718515028的专栏)

以前只用过vector<Object>  ,但是在做从数据库导出数据放到jtable中时,发现还有个vector<vector<Object>>的用法. 先说jtable和DefaultTableModel jtable本身是可以显示一张列表,但是不能按钮监听的增加正行数据. jtable中没有addRow(Object[] rowData) 或者addRow(Vector rowData) ,方法. 但是jtable中有一个构造方法,JTable(TableModel

浅谈Listview中Adpter的用法

一.Adpter用法: Adpter是连接后端数据与前端显示的适配器接口,是数据和UI(view)之间一个重要的纽带. 数据源       Adpter      Listview public class TaskListAdapter extends BaseAdapter 其中,BaseAdpter是一个继承类,继承他需要实现更多的方法,所以有更大的灵活性. 而ArrayAdpter支持泛型操作,只能显示一行字. SimpleAdapter有最好的扩充性,可以自定义出各种效果. Simpl

浅谈JS中 reduce() 的用法

过去有很长一段时间,我一直很难理解 reduce() 这个方法的具体用法,平时也很少用到它.事实上,如果你能真正了解它的话,其实在很多地方我们都可以用得上,那么今天我们就来简单聊聊JS中 reduce() 的用法. 一.语法 arr.reduce(function(prev,cur,index,arr){ ... }, init); 其中, arr 表示原数组: prev 表示上一次调用回调时的返回值,或者初始值 init; cur 表示当前正在处理的数组元素: index 表示当前正在处理的数

浅谈移动端rem的用法

一 什么是rem? “font size of the root element 这是w3c的定义 也就是说是相对于根节点(html节点)的字体大小的单位. 目前主流的浏览器基本都支持rem这个单位,大部份的默认字体单位是16px. 图片摘自 http://caniuse.mojijs.com/Home/Html/item/key/rem/index.html  二 简单应用. 既然确定在各个主流浏览器都能食用的话,我们就放心大胆在移动端进行开发了. 举个例子说明 html{ font-size

浅谈mmap()和ioremap()的用法与区别

一.mmap()mmap()函数是用来将设备内存线性地址映射到用户地址空间.(1)首先映射基地址,再通过偏移地址寻址:(2)unsigned char *map_cru_base=(unsigned char * )mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, CRU_BASE);        *(volatile unsigned int *)(map_cru_base+ CRU_OFFSET) = 0xff

浅谈html标签

浅谈html各常用标签用法 标题标签:<h1>-<h6>来表示,使标题字体变粗. <br />换行标记 <hr />水平分隔符 &nbsp空格符 &copy版权符 <a href>a标签超链接 href可接链接地址 <p>段落标签<blockquote>引用标签及可用做缩进 <table>表格中的<ul>无序列表<ol>有序列表<dl>自定义列表<row

浅谈 Android Service

 浅谈Android Service的基本用法: 关于Service最基本的用法自然是启动和停止操作. 启动Service有两种方式: 1.通过startService(Intent intent)方式启动,启动时会自动执行onCreate(),onStartCommand()方法. 2.通过bindService(Intent intent,ServiceConnection connection,int flag) 第一个参数是一个Intent对象,第二个参数是连接Service的实例,

浅谈struts2标签中的2个很常用的标签的用法(radio和select)

1.如图所示我们需要在前台的页面通过radio和select将对应的数据库中的数据显示到选项当中,这也是我们做项目中经常需要做的,动态的显示,而不是静态的显示. 首先我们需要在页面中导入struts2的标签库<%@ taglib prefix="s" uri="/struts-tags"%>,一般的我们不用struts2写一个radio代码如下: <input type="RADIO" name="sex"