在上篇文章中,我已经给大家列了一个在主线程中实现的方式,这篇文章来给大家说说使用Invoke的方式的例子:

对于不代理不太熟悉的朋友,建议先查查相关资料;

例子一:

在C#中,直接在子线程中对窗体上的控件操作是会出现异常,这是由于子线程和运行窗体的线程是不同的空间,因此想要在子线程来操作窗体上的控件,是不可能简单的通过控件对象名来操作,但不是说不能进行操作,微软提供了Invoke的方法,其作用就是让子线程告诉窗体线程来完成相应的控件操作。

要实现该功能,有两种方法可以选择:

 

1、在程序初始化的时候对要操作的控件设置下面的属性:

System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls  =  false;

这样,系统就不会再抛出上面所说的这个错误了。

从实质上说,该方法是通过采用取消线程安全保护模式的方式实现的,所以不建议采用。

 

2、基本思路如下:

把想对另一线程中的控件实施的操作放到一个函数中,然后使用delegate代理那个函数,并且在那个函数中加入一个判断,用InvokeRequired 来判断调用这个函数的线程是否和控件线程处于同一线程中,如果是则直接执行对控件的操作,否则利用该控件的Invoke或BeginInvoke方法来执行这个代理。示例代码如下:



C#多线程应用:子线程更新主窗体控件的值(二)_控件

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 /// <summary>
14 /// 定义一个队列,用于记录用户创建的线程
15 /// 以便在窗体关闭的时候关闭所有用于创建的线程
16 /// </summary>
17 private List<Thread> ChaosThreadList;
18
19 /***************************************************** 该类的初始化相关函数 ****************************************************/
20
21 /// <summary>
22 /// 窗体的初始化函数,初始化线程队列ChaosThreadList
23 /// </summary>
24 public frmMain()
25 {
26 InitializeComponent();
27 ChaosThreadList = new List<Thread>();
28 }
29
30 /// <summary>
31 /// 窗体的关闭事件处理函数,在该事件中将之前创建的线程全部终止
32 /// </summary>
33 /// <param name="sender"></param>
34 /// <param name="e"></param>
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 /// <summary>
50 /// 定义一个代理
51 /// </summary>
52 /// <param name="index"></param>
53 /// <param name="MSG"></param>
54 private delegate void DispMSGDelegate(int index,string MSG);
55
56 /// <summary>
57 /// 定义一个函数,用于向窗体上的ListView控件添加内容
58 /// </summary>
59 /// <param name="iIndex"></param>
60 /// <param name="strMsg"></param>
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 /// <summary>
83 /// 定义一个线程函数,用于循环向列表中添加数据
84 /// </summary>
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 /// <summary>
97 /// 【开始】按钮的单击事件处理函数,新建一个线程向窗体上的ListView控件填写内容
98 /// </summary>
99 /// <param name="sender"></param>
100 /// <param name="e"></param>
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 }


C#多线程应用:子线程更新主窗体控件的值(二)_控件


 

 

这样子就可以实现用子线程去操作主线程窗体上的控件的内容,同时,又不影响主线程对窗体上其他控件的响应。程序运行截图如下:

 

 C#多线程应用:子线程更新主窗体控件的值(二)_主线程_03

 

 点击[开始]按钮,程序开启一个新的线程,不断向列表中添加新的数据,而同时不会影响主界面对其它控件(例如:文本框)的响应。

 

[P.S]:

INVOKE方法的作用:

它使该控件所在的线程执行Invoke方法参数中指定的代理,也就是使主线程执行我们想对控件进行的操作。

 -----------------------------------------------------------------------------

例子二:

本文将详细介绍C#利用子线程如何刷新主线程,需要了解更多的朋友可以参考下

要求:如下图,使用线程操作

1、实时显示当前时间

2、输入加数和被加数,自动出现结果

C#多线程应用:子线程更新主窗体控件的值(二)_初始化_04

分析:两个问题解决的方式相同,使用子线程进行时间操作和加法操作,然后刷新主线程的控件显示结果

复制代码 代码如下:

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

}

}

是不是很简单呢?

 ​