- C#网络编程高级篇之网页游戏辅助程序设计
- 张慧斌 王小峰 郝爱峰
- 1323字
- 2020-08-27 07:41:32
1.4 在线程中调用窗口控件
在执行本章【例1.2】多线程例子时,可能有些读者会感到奇怪,为什么不去执行菜单中的【调试】下的【启动调试(S)】,而是执行【开始执行(不调试)】来运行程序呢?可能有些读者已经发现,如果执行【调试】下的【启动调试(S)】菜单的话,程序会报告错误,告诉用户不能在线程中调用控件。
默认情况下,C#不允许在一个线程中直接操作另一个线程中的控件,这是因为访问Windows窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的bug,以及不同线程争用控件引起的死锁问题。因此确保以线程安全方式访问控件非常重要。
在调试器中运行应用程序时,如果创建某控件的线程之外的其他线程试图调用该控件,则调试器会引发一个InvalidOperationException异常,并提示消息:“从不是创建控件的线程访问它”。
但是在Windows应用程序中,为了在窗体上显示线程中处理的信息,我们可能需要经常在一个线程中引用另一个线程中的窗体控件。比较常用的办法之一是使用委托(delegate)来完成这个工作。
为了区别是否是创建控件的线程访问该控件对象,Windows应用程序中的每一个控件对象都有一个InvokeRequired属性,用于检查是否需要通过调用Invoke方法来完成其他线程对该控件的操作,如果该属性为true,说明是其他线程操作该控件,这时可以创建一个委托实例,然后调用控件对象的Invoke方法,并传入需要的参数完成相应操作,否则可以直接对该控件对象进行操作,从而保证了安全代码下线程间的互操作。例如:
delegate void AppendStringDelegate(string str); private void AppendString(string str) { if (richTextBox1.InvokeRequired) { AppendStringDelegate d=new AppendStringDelegate (AppendString); richTextBox1.Invoke(d, "abc"); } else { richTextBox1.Text += str; } }
在这段代码中,首先判断是否需要通过委托调用对richTextBox1的操作,如果需要,则创建一个委托实例,并传入需要的参数来完成else代码块的功能;否则直接执行else代码块中的内容。
实际上,由于在编写程序时就已经知道控件是在哪个线程中创建的,因此也可以在不是创建控件的线程中直接调用控件对象的Invoke方法来完成对该线程中的控件的操作。
注意,不论是否判断InvokeRequired属性,委托中参数的个数和类型必须与传递给委托的方法需要的参数个数和类型完全相同。
【例1.10】修改本章例1.2程序,使其可以执行【启动调试(S)】,而不会出现“从不是创建控件的线程访问它”的异常现象。
新建一个ThreadEx110的Windows应用程序,代码如下所示。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; //引入多线程命名空间 namespace ThreadEx102 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } Thread thread; private void btnStart_Click(object sender, EventArgs e) { ThreadStart threadStart=new ThreadStart(ThreadAddItem); //声明一个线程 thread=new Thread(threadStart); //启动线程 thread.Start(); } private delegate void AddItem(); //定义一个线程委托 public void ThreadAddItem() { AddItem AddIt=new AddItem(Threading); //实例化一个委托 //在拥有此控件的基础窗体句柄的线程上执行指定的委托 this.Invoke(AddIt); } //自定义一个方法Threading,主要用于委托的调用 public void Threading() { for (int index=0; index < 100000; index++) { this.lstTest.Items.Add(string.Format("Item {0}", index)); } thread.Abort(); } private void btnLook_Click(object sender, EventArgs e) { MessageBox.Show(string.Format("ListBox中一共有{0}项", this.lstTest.Items.Count)); } } }
【例1.11】 一个线程操作另一个线程的控件的方法。新建一个名为ThreadControlEx111的Windows应用程序,界面设计如图1-13所示,程序运行界面如图1-14所示。
图1-13 例1.11程序界面
图1-14 例1.11程序运行界面
程序代码如下所示。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; namespace ThreadControlEx109 { public partial class Form1 : Form { Thread thread1; Thread thread2; delegate void AppendStringDelegate(string str); AppendStringDelegate appendStringDelegate; public Form1() { InitializeComponent(); appendStringDelegate=new AppendStringDelegate (AppendString); } private void AppendString(string str) { richTextBox1.Text += str; } private void Method1() { while (true) { Thread.Sleep(100); //线程1休眠100毫秒 richTextBox1.Invoke(appendStringDelegate, "a"); } } private void Method2() { while (true) { Thread.Sleep(100); //线程2休眠100毫秒 richTextBox1.Invoke(appendStringDelegate, "b"); } } private void btnStart_Click(object sender, EventArgs e) { richTextBox1.Text=""; thread1=new Thread(new ThreadStart(Method1)); thread2=new Thread(new ThreadStart(Method2)); thread1.Start(); thread2.Start(); } private void btnStop_Click(object sender, EventArgs e) { thread1.Abort(); thread1.Join(); thread2.Abort(); thread2.Join(); MessageBox.Show("线程1、2终止成功"); } } }