多线程(异步委托)调用和控件响应问题

    当系统要进行工作量较大的任务时,倾向于将该任务安排在一个优先级别相对较低的后台线程处理,这样UI线程或主线程还将可以响应用户的其他操作,使系统具有良好的交互性。下面以一个控件刷新的程序为例,讨论多线程和控件的关系。程序代码如下:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Text;
  7. using System.Windows.Forms;
  8. using System.Threading;
  9. namespace WindowsApplication3
  10. {
  11.     public partial class FormTest : Form
  12.     {
  13.         private delegate void InvokeDelegate();
  14.         private delegate void SetTextBoxHandler(TextBox tb,string str);
  15.         private InvokeDelegate invokeDelegate;
  16.         private SetTextBoxHandler deleChangeText;
  17.         public FormTest()
  18.         {
  19.             InitializeComponent();
  20.         }
  21.         private void FormTest_Load(object sender, EventArgs e)
  22.         {
  23.             //初始化两个代理实例
  24.             this.invokeDelegate = new InvokeDelegate(this.FunTest);
  25.             this.deleChangeText = new SetTextBoxHandler(this.SetTextBoxValue);
  26.         }
  27.         private void FunTest()
  28.         {
  29.             for (int i = 0; i < 100000; i++)
  30.             {
  31.                 //搜索控件句柄,判断控件是否生成,不生成则返回false,此时就不能调用begininvoke方法了
  32.                 //另外,如果对if内部语句的调用发生在同一线程(即UI线程或主线程),则InvokeRequired将返回false;
  33.                 if (this.textBox1.InvokeRequired)
  34.                 {
  35.                     this.BeginInvoke(this.deleChangeText, new object[] { this.textBox1, i.ToString() });
  36.                     //一定注意,Sleep的动作一定不能放在SetTextBoxValue()方法中,因为控件的BeginInvoke()会将SetTextBoxValue()方法
  37.                     //封送至后台线程,这就变成了在后台线程中让主线程Sleep300ms的时间,而不是让后台线程Sleep300ms了。
  38.                     Thread.Sleep(300);
  39.                 }
  40.                 else
  41.                 {
  42.                     this.SetTextBoxValue(this.textBox1, i.ToString());
  43.                     Thread.Sleep(300);
  44.                 }
  45.             }
  46.             
  47.         }
  48.         private void SetTextBoxValue(TextBox tb, string str)
  49.         {
  50.             tb.Text = str;
  51.             tb.Refresh();
  52.             //下面的句子表明,即使该方法被封送到后台线程后,该方法内的线程是主线程,而非后台线程
  53.             //MessageBox.Show(Thread.CurrentThread.IsBackground.ToString());
  54.         }
  55.         //在后台线程中调用FunTest()
  56.         private void button1_Click(object sender, EventArgs e)
  57.         {
  58.             //使用异步调用,开辟后台线程
  59.             this.invokeDelegate.BeginInvoke(nullnull);
  60.         }
  61.         //在UI线程中调用FunTest()
  62.         private void button2_Click(object sender, EventArgs e)
  63.         {
  64.             this.FunTest();
  65.         }
  66.         //使用代理调用函数,结果同button2_Click()
  67.         private void button3_Click(object sender, EventArgs e)
  68.         {
  69.             this.invokeDelegate();
  70.         }
  71.     }
  72. }
 

 

 

代码的主要目的是,点击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来分别给出正确的调用方法。本例子中点击button2button3所调用的方法,就是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了。

 

*当子线程是前台线程,则主线程结束并不影响其他线程的执行,只有所有前台线程都结束,程序结束 

*当子线程是后台线程,则主线程的结束,会导致子线程的强迫结束

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值