在上篇文章中,我已经给大家列了一个在主线程中实现的方式,这篇文章来给大家说说使用Invoke的方式的例子:
对于不代理不太熟悉的朋友,建议先查查相关资料;
例子一:
在C#中,直接在子线程中对窗体上的控件操作是会出现异常,这是由于子线程和运行窗体的线程是不同的空间,因此想要在子线程来操作窗体上的控件,是不可能简单的通过控件对象名来操作,但不是说不能进行操作,微软提供了Invoke的方法,其作用就是让子线程告诉窗体线程来完成相应的控件操作。
要实现该功能,有两种方法可以选择:
1、在程序初始化的时候对要操作的控件设置下面的属性:
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;
这样,系统就不会再抛出上面所说的这个错误了。
从实质上说,该方法是通过采用取消线程安全保护模式的方式实现的,所以不建议采用。
2、基本思路如下:
把想对另一线程中的控件实施的操作放到一个函数中,然后使用delegate代理那个函数,并且在那个函数中加入一个判断,用InvokeRequired 来判断调用这个函数的线程是否和控件线程处于同一线程中,如果是则直接执行对控件的操作,否则利用该控件的Invoke或BeginInvoke方法来执行这个代理。示例代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Windows.Forms; 4 5 using System.Threading; 6 7 namespace 子线程操作主线程窗体上的控件 8 { 9 public partial class frmMain : Form 10 { 11 /***************************************************** 定义该类的私有成员 ****************************************************/ 12 13 ///14 /// 定义一个队列,用于记录用户创建的线程 15 /// 以便在窗体关闭的时候关闭所有用于创建的线程 16 /// 17 private ListChaosThreadList; 18 19 /***************************************************** 该类的初始化相关函数 ****************************************************/ 20 21 /// 22 /// 窗体的初始化函数,初始化线程队列ChaosThreadList 23 /// 24 public frmMain() 25 { 26 InitializeComponent(); 27 ChaosThreadList = new List(); 28 } 29 30 /// 31 /// 窗体的关闭事件处理函数,在该事件中将之前创建的线程全部终止 32 /// 33 /// 34 /// 35 private void frmMain_FormClosed(object sender, FormClosedEventArgs e) 36 { 37 if (ChaosThreadList.Count > 0) 38 { 39 //编列自定义队列,将所有线程终止 40 foreach (Thread tWorkingThread in ChaosThreadList) 41 { 42 tWorkingThread.Abort(); 43 } 44 } 45 } 46 47 /***************************************************** 定义该类的自定义函数 ****************************************************/ 48 49 ///50 /// 定义一个代理 51 /// 52 /// 53 /// 54 private delegate void DispMSGDelegate(int index,string MSG); 55 56 ///57 /// 定义一个函数,用于向窗体上的ListView控件添加内容 58 /// 59 /// 60 /// 61 private void DispMsg(int iIndex,string strMsg) 62 { 63 if (this.lstMain.InvokeRequired==false) //如果调用该函数的线程和控件lstMain位于同一个线程内 64 { 65 //直接将内容添加到窗体的控件上 66 ListViewItem lvi = new ListViewItem(); 67 lvi.SubItems[0].Text = iIndex.ToString(); 68 lvi.SubItems.Add(strMsg); 69 this.lstMain.Items.Insert(0, lvi); 70 } 71 else //如果调用该函数的线程和控件lstMain不在同一个线程 72 { 73 //通过使用Invoke的方法,让子线程告诉窗体线程来完成相应的控件操作 74 DispMSGDelegate DMSGD = new DispMSGDelegate(DispMsg); 75 76 //使用控件lstMain的Invoke方法执行DMSGD代理(其类型是DispMSGDelegate) 77 this.lstMain.Invoke(DMSGD, iIndex, strMsg); 78 79 } 80 } 81 82 ///83 /// 定义一个线程函数,用于循环向列表中添加数据 84 /// 85 private void Thread_DisplayMSG() 86 { 87 for (int i = 0; i < 10000; i++) 88 { 89 DispMsg(i + 1, "Welcome you : " + (i + 1).ToString()); 90 Thread.Sleep(10); 91 } 92 } 93 94 /***************************************************** 定义该类的事件处理函数 ****************************************************/ 95 96 ///97 /// 【开始】按钮的单击事件处理函数,新建一个线程向窗体上的ListView控件填写内容 98 /// 99 /// 100 /// 101 private void btnBegin_Click(object sender, EventArgs e)102 {103 //创建一个新的线程104 Thread tWorkingThread = new Thread(Thread_DisplayMSG);105 106 //将新建的线程加入到自定义线程队列中,以便在窗体结束时关闭所有的线程107 ChaosThreadList.Add(tWorkingThread);108 109 //开启线程110 tWorkingThread.Start();111 } 112 113 }114 }
这样子就可以实现用子线程去操作主线程窗体上的控件的内容,同时,又不影响主线程对窗体上其他控件的响应。程序运行截图如下:
点击[开始]按钮,程序开启一个新的线程,不断向列表中添加新的数据,而同时不会影响主界面对其它控件(例如:文本框)的响应。
[P.S]:
INVOKE方法的作用:
它使该控件所在的线程执行Invoke方法参数中指定的代理,也就是使主线程执行我们想对控件进行的操作。
出处:
--------------------------------------------------------------------------------------------------------------------
例子二:
本文将详细介绍C#利用子线程如何刷新主线程,需要了解更多的朋友可以参考下
要求:如下图,使用线程操作 1、实时显示当前时间 2、输入加数和被加数,自动出现结果 分析:两个问题解决的方式相同,使用子线程进行时间操作和加法操作,然后刷新主线程的控件显示结果 复制代码 代码如下:using System; using System.Threading; using System.Windows.Forms; namespace WinThread { public partial class frmMain : Form { public frmMain() { InitializeComponent(); } /// <summary> /// 初始化 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void frmMain_Load(object sender, EventArgs e) { // 控件初始化 this.txtOne.Text = "0"; this.txtSecond.Text = "0"; // 显示时间操作 Thread showTimeThread = new Thread(new ThreadStart(GetTime)); showTimeThread.IsBackground = true; showTimeThread.Start(); // 加法操作 Thread addThread = new Thread(new ThreadStart(Add)); addThread.IsBackground = true; addThread.Start(); } #region 显示时间操作 /// <summary> /// 取得实时时间 /// </summary> private void GetTime() { try { while (true) { string currentTime = string.Format("{0}.{1}", DateTime.Now.ToLongTimeString(), DateTime.Now.Millisecond); ShowTime(currentTime); Application.DoEvents(); } } catch (Exception ex) { Console.WriteLine(ex.Message); } } // 定义显示时间操作委托,用于回调显示时间方法 delegate void ShowTimeCallBack(string str); /// <summary> /// 实时显示时间 /// </summary> /// <param name="str"></param> private void ShowTime(string str) { if (this.txtCurrentTime.InvokeRequired) { ShowTimeCallBack showTimeCallBack = new ShowTimeCallBack(ShowTime); this.Invoke(showTimeCallBack, new object[] { str }); } else { this.txtCurrentTime.Text = str; } } #endregion #region 加法操作 /// <summary> /// 加法操作 /// </summary> private void Add() { try { while (true) { int i = Convert.ToInt32(this.txtOne.Text.Trim()); int j = Convert.ToInt32(this.txtSecond.Text.Trim()); ShowResult((i + j).ToString()); Application.DoEvents(); } } catch (Exception ex) { Console.WriteLine(ex.Message); } } // 定义加法操作委托,用于回调加法操作方法 delegate void ShowResultCallBack(string result); /// <summary> /// 显示结果 /// </summary> /// <param name="result"></param> private void ShowResult(string result) { if (this.txtResult.InvokeRequired) { // 写法1 //ShowResultCallBack showResultCallBack = new ShowResultCallBack(ShowResult); //this.Invoke(showResultCallBack, new object[] { result }); // 写法2 //使用委托来赋值 this.txtResult.Invoke( //委托方法 new ShowResultCallBack(ShowResult), new object[] { result }); } else { this.txtResult.Text = result; } } #endregion } }是不是很简单呢?
出处参考: