- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Drawing;
- using System.Text;
- using System.Windows.Forms;
- using System.Threading;
- namespace WindowsApplication3
- {
- public partial class FormTest : Form
- {
- private delegate void InvokeDelegate();
- private delegate void SetTextBoxHandler(TextBox tb,string str);
- private InvokeDelegate invokeDelegate;
- private SetTextBoxHandler deleChangeText;
- public FormTest()
- {
- InitializeComponent();
- }
- private void FormTest_Load(object sender, EventArgs e)
- {
- //初始化两个代理实例
- this.invokeDelegate = new InvokeDelegate(this.FunTest);
- this.deleChangeText = new SetTextBoxHandler(this.SetTextBoxValue);
- }
- private void FunTest()
- {
- for (int i = 0; i < 100000; i++)
- {
- //搜索控件句柄,判断控件是否生成,不生成则返回false,此时就不能调用begininvoke方法了
- //另外,如果对if内部语句的调用发生在同一线程(即UI线程或主线程),则InvokeRequired将返回false;
- if (this.textBox1.InvokeRequired)
- {
- this.BeginInvoke(this.deleChangeText, new object[] { this.textBox1, i.ToString() });
- //一定注意,Sleep的动作一定不能放在SetTextBoxValue()方法中,因为控件的BeginInvoke()会将SetTextBoxValue()方法
- //封送至后台线程,这就变成了在后台线程中让主线程Sleep300ms的时间,而不是让后台线程Sleep300ms了。
- Thread.Sleep(300);
- }
- else
- {
- this.SetTextBoxValue(this.textBox1, i.ToString());
- Thread.Sleep(300);
- }
- }
- }
- private void SetTextBoxValue(TextBox tb, string str)
- {
- tb.Text = str;
- tb.Refresh();
- //下面的句子表明,即使该方法被封送到后台线程后,该方法内的线程是主线程,而非后台线程
- //MessageBox.Show(Thread.CurrentThread.IsBackground.ToString());
- }
- //在后台线程中调用FunTest()
- private void button1_Click(object sender, EventArgs e)
- {
- //使用异步调用,开辟后台线程
- this.invokeDelegate.BeginInvoke(null, null);
- }
- //在UI线程中调用FunTest()
- private void button2_Click(object sender, EventArgs e)
- {
- this.FunTest();
- }
- //使用代理调用函数,结果同button2_Click()
- private void button3_Click(object sender, EventArgs e)
- {
- this.invokeDelegate();
- }
- }
- }
代码的主要目的是,点击button,使得控件textbox控件的值从1刷新到100000,主要有三种方式,第一,异步委托调用;第二,直接调用;第三,代理同步调用。
经过试验发现一下特点:
第一,三者都可以刷新textbox,但是后面二者不能响应窗体其他事件,但使用异步委托的话,窗体可以相应拖放、点击button等事件,拖放窗体时,textbox仍持续刷新。
第二, 使用异步委托或者多线程调用FunTest()方法,涉及到对UI控件的操作,本例中表现为对textbox设置text属性及刷新,而在后台线程中操作控件很容易引起异常,使得线程使用不安全。一般情况下,不鼓励在后台线程中操作控件。.Net提供了Control.BeginInvoke()等方法来解决这一问题。倘若后台线程需要操作控件,则必须将这些操作函数绑定到一个代理D上,然后通过控件的BeginInvoke()方法调用,D作为该方法的一个参数。在例程中,操作控件的方法为SetTextBoxValue(),使用代理deleChangeText绑定,然后,通过BeginInvoke()调用。
第三, 控件的InvokeRequired属性的作用。如果FunTest()方法在非UI线程中执行,则InvokeRequired返回true,若在UI线程中执行,则返回false。在例子中使用if else流程控制语句,若FunTest()在后台线程中执行,则必须使用控件的BeginInvoke()方法,若FunTest()在UI线程中执行,则可直接调用控件操作方法,无需使用BeginInvoke()方法。注意FunTest()既可能在后台线程中调用,也可能在UI线程中调用,而在不同的线程内操作控件的方法有所不同,因此,必须使用InvokeRequired属性和if else来分别给出正确的调用方法。本例子中点击button2和button3所调用的方法,就是else分支的方法,因为这两种调用方法不属于多线程调用。
第四, 关于FunTest()方法中的Thread.Sleep(300)。在不加这一sleep操作时,在对四台计算机的试验中,运行时,三台计算机的FormTest窗体可以拖动,一台不可以拖动。添加sleep操作后,所有计算机的FormTest窗口都可以拖动。我猜想,虽然实现的是多线程操作,但计算机的性能不同,是否能够响应窗体事件也不一样。当我不停的点击button1,不停的开辟新线程的时候,sleep参数设置为300时,最多开辟62个线程后,窗体不再响应事件,button1也无法再点击;当sleep设置为200时,不停点击button1,最多开辟61个线程,此后窗体不再响应事件。可见,虽然多线程可以提高UI线程的响应,但这并不是绝对的,随着计算机性能的下降,UI线程的响应能力就越差,直到无法响应。
第五, Thread.Sleep(300)的位置问题。是否可以将Thread.Sleep(300)放到SetTextBoxValue()方法中?实验证明,这样做是不行的,我们希望sleep的线程是后台线程而非UI线程,一旦Sleep()方法放到SetTextBoxValue()方法中,SetTextBoxValue()将被封送至后台线程中运行,然后这里的Sleep,实际上是UI线程的Sleep,而不是后台线程的Sleep了。
*当子线程是前台线程,则主线程结束并不影响其他线程的执行,只有所有前台线程都结束,程序结束
*当子线程是后台线程,则主线程的结束,会导致子线程的强迫结束