不少人对Invoke和BeginInvoke理解不深入,不知道该怎么应用,在这篇博文里将详细阐述Invoke和BeginInvoke的用法:编程
首先说下Invoke和BeginInvoke有两种用法:安全
1.Control中Invoke,BeginInvoke多线程
2.Delegate中Invokke,BeginInvoke异步
这两种状况是不一样的,咱们首先讲一下第一种,也就是Control类中的Invoke,BeginInvoke的用法。咱们先来看一下MSDN是如何解释的:async
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"); }
运行结果为:ui
从结果能够看出执行时首先执行AAA,而后CCC和BBB同时执行,Invoke将Invoke方法提交给主线程,主线程执行完运行在Invoke上的方法,而后才执行子线程的方法,简单讲同步就是说必须等带Invoke上的方法执行完,才能执行子线程的方法。this
再来看看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. 若是后台任务要求必须按照特定顺序执行,或者必须访问共享资源,则异步编程不适合,而应该直接采用多线程开发技术。