在以前的章节中,我们不只一次的提到过,不能在非创建UI控件的线程中操作UI元素,否则会和UI控件创建线程(一般是主线程)产生冲突,造成不可预料的后果。
该如何解决这个问题呢?除了上一节所讲的BackgroundWorker和Timer以外,微软将Control类实现了ISynchronizeInvoke接口,提供了Invoke和BeginInvoke方法来提供让其它线程更新GUI界面控件的机制。
下边还是通过一个例子给大家讲解一下Control.Invoke()和Control.BeginInvoke();
首先新建一个WinForm应用程序,在Form窗体上做如下布局:
然后,新建一个委托
public delegate void ShowTime();
在Control。Invoke()按钮的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、
由实验结果我们可以得出以下结论: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();//改变Button的Text属性为当前的时间
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(showTime,100);语句之后,应该马上执行下边一句MessageBox.Show();最后再执行委托方法,为了对此进行测试,我在Time()函数里将线程休眠了3秒钟,但是事实证明,只有在Time()方法执行完之后,才会执行MessageBox.show()语句,才会出现上边所示的结果,所谓的异步执行,在这里并没有得到体现,这是为什么呢?让我们继续往下看。
由以上的例子可知Control的Invoke和BeginInvoke的委托方法是在主线程,即UI线程上执行的。如果委托方法不会花费很长的时间,那直接用Control.Invoke()和Control.BeginInvoke()方法,如果你的委托方法用来取花费时间长的数据,然后更新界面什么的,千万别在UI线程上调用Control.Invoke和Control.BeginInvoke,因为这些是依然阻塞UI线程的,造成界面的假死,那么我们该该如何办呢?最好的解决办法就是重新建立一个线程,然后在线程调用函数里再启用Control.Invoke()或者Control.BeginInvoke()
下面还是给大家做一个示例,然后在示例中慢慢讲解。
在Form窗体里放置2个Button如图
先创建一个委托 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
}
实验结果如下:
由实验结果可知,在线程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()方法结束!");
}
由实验结果可知:在线程thread调用的ShowThread方法里,启动了Control.BeginInvoke()方法,Control.BeginInvoke()方法调用的委托方法ShowResult()之后,马上就执行了ShowThread()里的MessageBox.Show("E,我最后执行!,Thread调用Contorol.Invoke()方法结束!"),并没有等待委托方法ShowResult()执行完,由于ShowResult()执行时间较长,故最后才显示消息框4,所以说这里的Control.BeginInvoke()是异步操作。这样就可以与Control.Invoke()是同步操作明显的区分开了。
说到BeginInvoke()和Invoke()还有朋友问Delegate的BeginInvoke()、Invoke()和Contorl.BeginInvoke()、Invoke()的区别,之前的章节中有介绍过Delegate的BeginInvoke(),委托的BeginInvoke()主要用于异步操作,其本质是在线程池里又启用了一个新的后台线程,而Contorl.BeginInvoke()它本身就是在创建控件的线程里(也就是UI)线程里执行的,它主要依附于具体的UI元素,可以直接的访问UI元素,但是通过委托的BeginInvoke()是绝对不能访问UI元素的,这点已经强调了很多次了。
好了,关于《线程实用详解》这一系列的文章到这里已经结束了,通过学习、总结,自己也收获了很多的东西,当然,还有有些地方总结的还不够全面,还需要继续的努力。学习就是这样一个过程,通过不断的练习、反思、总结然后再反思、练习、总结、升华。希望能给大家带来些帮助,也还请大家不吝赐教,共同交流探讨。