在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
invoke和begininvoke 区别一直对invoke和begininvoke的使用和概念比较混乱,这两天看了些资料,对这两个的用法和原理有了些新的认识和理解。 首先说下,invoke和begininvoke的使用有两种情况: 1. control中的invoke、begininvoke。 2. delegrate中的invoke、begininvoke。 这两种情况是不同的,我们这里要讲的是第1种。下面我们在来说下.NET中对invoke和begininvoke的官方定义。 control.invoke(参数delegate)方法:在拥有此控件的基础窗口句柄的线程上执行指定的委托。 control.begininvoke(参数delegate)方法:在创建控件的基础句柄所在线程上异步执行指定委托。 根据这两个概念我们大致理解invoke表是同步、begininvoke表示异步。但是如何来进行同步和异步呢?我们来做一个测试。 private void button1_Click(object sender, EventArgs e) { MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "AAA"); Thread 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); } private void StartMethod() { MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "CCC"); button1.Invoke((EventHandler)(delegate { MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "EEE"); })); MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "DDD"); } 论:我们运行后,看下程序的运行顺序,1AAA->2CCC和1BBB->1EEE ->2DDD 。 解释:主线程运行1AAA,然后1BBB和子线程2CCC同时执行,然后通过invoke来将方法提交给主线程,然后子线 程等待主线程执行,直到主线程将invokemethod方法执行完成(期间必须等待主线程的任务执行完成,才会去执行invoke提交的任务),最后执 行子线程2DDD。
begininvoke 例子: StringBuilder stb = new StringBuilder(); private void button1_Click(object sender, EventArgs e) { textBox1.Text = ""; stb.Clear(); stb.Append(Thread.CurrentThread.GetHashCode().ToString() + "AAA"); //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "AAA"); Thread invokeThread = new Thread(new ThreadStart(StartMethod)); invokeThread.Start(); string a = string.Empty; for (int i = 0; i < 3; i++) //调整循环次数,看的会更清楚 { Thread.Sleep(2000); //Thread.Sleep(200); a = a + "B"; } //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + a); stb.Append(Thread.CurrentThread.GetHashCode().ToString() + a); //textBox1.Text = stb.ToString(); } private void StartMethod() { //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "CCC"); stb.Append(Thread.CurrentThread.GetHashCode().ToString() + "CCC"); button1.Invoke((EventHandler)(delegate { //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "EEE"); stb.Append(Thread.CurrentThread.GetHashCode().ToString() + "EEE"); })); //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "DDD"); stb.Append(Thread.CurrentThread.GetHashCode().ToString() + "DDD"); } private void button2_Click(object sender, EventArgs e) { textBox1.Text = ""; stb.Clear(); //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "AAA"); stb.Append(Thread.CurrentThread.GetHashCode().ToString() + "AAA"); Thread invokeThread = new Thread(new ThreadStart(StartMethod)); invokeThread.Start(); string a = string.Empty; for (int i = 0; i < 3; i++) //调整循环次数,看的会更清楚 { Thread.Sleep(100); a = a + "B"; } //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + a); stb.Append(Thread.CurrentThread.GetHashCode().ToString() + a); } private void StartMethod2() { //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "CCC"); stb.Append(Thread.CurrentThread.GetHashCode().ToString() + "CCC"); button1.BeginInvoke((EventHandler)(delegate { //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "EEE"); stb.Append(Thread.CurrentThread.GetHashCode().ToString() + "EEE"); })); //MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "DDD"); stb.Append(Thread.CurrentThread.GetHashCode().ToString() + "DDD"); } private void button3_Click(object sender, EventArgs e) { textBox1.Text = stb.ToString(); } textbox1 的显示 1AAA3CCC3DDD1BBB1EEE
结论: 我们运行后看看执行的结果:1AAA->3CCC和3DDD->1BBB->1EEE 解释: 主线程运行1AAA,然后1BBB和子线程3CCC同时执行,然后通过begininvoke来将invokemethod方法提交给主线程,然后主线程执行1EEE(主线程自己的任务执行完成), 同时子线程继续执行3DDD。
通过这个两段代码的测试比较,我们会发现其实invoke和begininvoke所提交的委托方法都是在主线程中执行的,其实根据我invoke 和begininvoke的定义我们要在子线程中来看这个问题,在invoke例子中我们会发现invoke所提交的委托方法执行完成后,才能继续执行 DDD;在begininvoke例子中我们会发现begininvoke所提交的委托方法后,子线程继续执行DDD,不需要等待委托方法的完成。 那么现在我们在回想下invoke(同步)和begininvoke(异步)的概念,其实它们所说的意思是相对于子线程而言的,其实对于控件的调用总是由 主线程来执行的。我们很多人搞不清这个同步和异步,主要还是因为我们把参照物选错了。其实有时候光看概念是很容易理解错误的。 解决从不是创建控件的线程访问它
在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,Invoke 和 BeginInvoke 就是为了解决这个问题而出现的,使你在多线程中安全的更新界面显示。
using System.Threading;
public delegate void MyInvoke(string str1, string str2); public Form2() { InitializeComponent(); } public void DoWork() { //this.textBox1.Text = "我是文本框 "; // 错误 提示 线程间操作无效 从不是创建控件textBox1的线程访问它 MyInvoke mi = new MyInvoke(UpdateForm); this.BeginInvoke(mi, new Object[] { "我是文本框", "haha" }); } private void button1_Click(object sender, EventArgs e) { Thread thr = new Thread(new ThreadStart(DoWork)); thr.Start(); } public void UpdateForm(string param1, string param2) { this.textBox1.Text = param1 + param2; } 注意代理的使用! 后面再次补充 在 WinForm开发过程中经常会用到线程,有时候还往往需要在线程中访问线程外的控件,比如:设置textbox的Text属性等等。如果直接设置程序必 定会报出:从不是创建控件的线程访问它,这个异常。通常我们可以采用两种方法来解决。一是通过设置control的属性。二是通过delegate,而通 过delegate也有两种方式,一种是常用的方式,另一种就是匿名方式。下面分别加以说明. 首先,通过设置control的一个属性值为false.我们可以在Form_Load方法中添加:Control.CheckForIllegalCrossThreadCalls=false;来解决。设置为false表示不对错误线程的调用进行捕获。这样在线程中对textbox的Text属性进行设置时就不会再报错了。
delegate void SafeSetText(string strMsg); private void SetText(string strMsg) { if(textbox1.InvokeRequired) { SafeSetText objSet=new SafeSetText(SetText); textbox1.Invoke(objSet,new object[]{strMsg}); } else { textbox1.Text=strMsg; } } 在线程内需要设置textbox的值时调用SetText方法既可。我们还可以采用另一种委托的方式来实现,那就是匿名代理,例如: delegate void SafeSetText(string strMsg); private void SetText2(string strMsg) { SafeSetText objSet = delegate(string str) { textBox1.Text = str; } textBox1.Invoke(objSet,new object[]{strMsg}); } 这样同样可以实现。
在C# 3.0及以后的版本中有了Lamda表达式,像上面这种匿名委托有了更简洁的写法。.NET Framework 3.5及以后版本更能用Action封装方法。例如以下写法可以看上去非常简洁:
void ButtonOnClick(object sender,EventArgs e)
{
this.Invoke(new Action(()=>
{
button.Text="关闭";
}));
}
|
请发表评论