在Framework2.0中,跨线程进行访问控件时,编译器会检查你所使用的方法是否线程安全。如VS2003那样直接无视线程赋值和取值的话,编译器会报错,提示线程不安全。几乎所有初次接触多线程WINFORM编程的人都碰到过这样的问题。
发生这类情况的时候,就可以使用委托和事件来回调创建该控件的线程中的方法。
所有控件都继承自 System.Windows.Form.Control类,而在Control类中已经包含了一系列线程安全的解决方案。
Control.InvokeRequired:
判断该该控件是否为当前的线程所创建的。
Control.Invoke(delegate, object [] params):
到创建该控件的线程中去调用委托所决定的方法。因为是委托,所以可以连续调用多个方法。
Control.BeginInvoke() & Control.EndInvoke():
异步方式执行操作,MSDN中有详细的方法
跨线程读取:
对于值类型的变量,跨线程的访问不存在安全问题。但对于引用类型,比如String类型,特别是继承自Control类的控件来说,JIT会抛出类型不安全的异常,解决方法是将需要的值从控件中取出放在值类型中,有两种方法实现:
1、在线程开始之前把数据写入值变量。
2、使用委托去读取控件的值。
以下举例说明
... {
private string[] _file;
public string[] Files
...{
get ...{ return this._file }
set ...{ this._file = value; }
}
public ThreadDemo1() ...{/**//*Constructor*/}
public void Run()
...{
foreach(string strItem in _file)
...{
Console.WriteLine( strItem );
Thread.Sleep(1000);
}
}
}
public delegate string GetStrHandler( int index); // 定义委托
class ThreadDemo2
... {
private int _nums;
public int Count
...{
get ...{ return this._nums; }
set ...{ this._nums = value; }
}
public event GetStrHandler onPrint; //定义onPrint事件
public ThreadDemo2() ...{/**//*Constructor*/}
public void Run()
...{
for (int i = 0; i < this._nums; i++ )
...{
Console.WriteLine( this.onPrint(i) ); //输出的同时触发事件
Thread.Sleep(1000);
...{
}
}
/**//**
*主窗体类
*//
class MainForm: Form
...{
//Some Form Initialization Code
//Button button1;
//Button button2;
//ListView lvItems;
//...............................................
private ThreadDemo1 td;
private ThreadDemo2 dtd;
private GetStrHandler GetStr;
//方法一,取值
public button1_Click(object sender, EventAvgs e)
...{
td = new ThreadDemo1();
td.Files = new string[lvItems.Itmes.Count];
foreach(ListViewItem Item in lvItems.Items)
...{
int index = Item.index;
td.Files[index] = new string(Item.Text); //创建新字符串,避免引用
}
Thread myThread = new Thread(new ThreadStart(td.Run));
myThread.Start();
}
//方法二,触发事件
public button2_Click(object sender, EventAvgs e)
...{
dtd = new ThreadDemo2();
dtd.onPrint += this.sendmsg();
Thread myThread = new Thread(new ThreadStart(dtd.Run));
myThread.Start();
}
private string sendmsg(int index)
...{
string Result = String.Empty;
if ( lvItems.InvokeRequired ) //判断是否需要使用Invoke方法,如果当前线程不是控件的创建线程,返回True
...{
GetStr = new GetStrHandler(this.sendmsg);
lvItems.Invoke( GetStr, new objcet[] ...{ index } );
}
else
...{
Result = lvItems.Items[index].Text;
}
return Result;
}
}
将以上代码中的sendmsg方法修改一下,就可以实现跨线程修改控件属性。比如:
... {
if ( lvItems.InvokeRequired ) //判断是否需要使用Invoke方法,如果当前线程不是控件的创建线程,返回True
...{
GetStr = new GetStrHandler(this.setmsg);
lvItems.Invoke( GetStr, new objcet[] ...{ index } );
}
else
...{
lvItems.Items[index].Text = "跨线程修改成功";
}
return Result;
}
这个例子只是简单的使用了委托和事件的基本特性演示了跨线程访问控件的方法。
虽然控件BackgroundWork提供了一种更简便的方法来进行后台任务(就是多线程),但是通过委托和事件可以定制非常复杂的多线程应用。