版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。
从.Net FrameWork2.0开始,为了加强了程序安全,防止跨线程调用导致不可预知的结果。微软将窗体主线程(UI控件线程)和其它线程分开,不允许从其它线程直接跨线程访问窗体主线程。在控制台下面使用多线程不用考虑操作界面的问题,但是在窗体界面下使用多线程,会因为访问控件方法或属性的线程不是创建该控件的线程,在编译调试时产生错误。
先看以下例子:
【例 15.23】【项目:code15-023】求1至10亿所有整数之和。
代码和【例 15.6】基本相同,但这次将求得的和显示在文本框中。
增加一个TextBox控件,将【例 15.6】的代码:
MessageBox.Show(sum.ToString());
修改为:
TextBox1.Text = sum.ToString();
运行代码会抛出一个“InvalidOperationException”异常:线程间操作无效:从不是创建控件"TextBox1"的线程访问它。
图15-21 跨线程访问异常
注意:直接运行编译后的exe程序,并不会产生错误。
5.3.10.1 CheckForIllegalCrossThreadCalls
CheckForIllegalCrossThreadCalls是Control类的一个属性,通过设置true或者false来指示程序是否捕获对错误线程的调用。如果需要跨线程设置UI,只需要让程序不捕获是否跨线程,将CheckForIllegalCrossThreadCalls设置为false即可。
【例 15.24】【项目:code15-024】使用CheckForIllegalCrossThreadCalls跨线程设置UI。
private void Form1_Load(object sender, EventArgs e)
{
//窗体的所有控件都不检查是否跨线程调用
CheckForIllegalCrossThreadCalls = false;
//以下代码TextBox不检查是否跨线程调用
//TextBox.CheckForIllegalCrossThreadCalls = false;
}
private void Button1_Click(object sender, EventArgs e)
{
//将求和的方法地址传递给Thread的构造函数
Thread th = new Thread(getSum);
//启动线程
th.Start();
}
//这里是求1-10亿所有整数的和
private void getSum()
{
Int64 sum = 0;
for (Int64 i = 1; i <= 1000000000; i++)
sum += i;
TextBox1.Text = sum.ToString();
}
运行结果如下图所示:
图15-22 正常输出计算结果
注意:这种方法只是简单地禁止了检查跨线程错误,是非线程安全的,可能会引发不可预料的线程错误,一般不建议使用。
15.3.10.2 使用委托和Invoke
窗体控件提供Invoke 方法,可以在拥有此控件的基础窗口句柄的线程上执行指定的委托,而在委托中完成UI控件修改。
Invoke方法其中一个重载:
public object Invoke (Delegate method, params object[] args);
参数说明:
- method:一个方法委托,它采用的参数的数量和类型与args参数中所包含的相同。
- args:作为指定方法的参数传递的对象数组。如果此方法没有参数,args可以设置为null。
注意:Invoke方法另外一个不带args参数的版本接受不带参数的委托。
使用委托和Invoke方法跨线程操作UI界面的基本步骤:
1、创建一个调用方法A,采用多线程调用方法C;
2、声明一个类级的委托B,其参数和调用方法C相同;
3、创建新线程要调用的方法C,在这个方法里面创建需要委托的方法实例D,并关联了E;
4、创建委托关联的方法E,在这个方法中修改UI;
5、在方法C中使用需要修改的控件UI的Invoke方法F。
另外,在方法C中可以先采用InvokeRequired属性判断是否必须调用Invoke方法。
【例 15.25】【项目:code15-025】使用委托和Invoke跨线程设置UI。
//委托B:声明一个委托,参数与要调用的方法相同
private delegate void showSum(Int64 sum);
//方法A
private void button1_Click(object sender, EventArgs e)
{
Thread th = new Thread(getSum);
th.Start();
}
//方法C:这里是求1-10亿所有整数的和
private void getSum()
{
Int64 sum = 0;
for (Int64 i = 1; i <= 1000000000; i++)
sum += i;
if (textBox1.InvokeRequired)
{
//D:获得委托的实例
showSum newsum = new showSum(showsumIntext);
//F:控件的Invoke方法调用方法并传递参数
textBox1.Invoke(newsum, sum);
}
else
textBox1.Text = sum.ToString();
}
//E:委托关联的方法,在这个方法中修改UI
private void showsumIntext(Int64 sum)
{
textBox1.Text = sum.ToString();
}
运行结果同【例 15.24】。
15.3.10.3 使用MethodInvoker 委托
MethodInvoker委托可执行托管代码中声明为 void 且不接受任何参数的任何方法,在调用控件的Invoke方法或需要一个简单委托又不想自己定义时可以使用该委托。
【例 15.26】【项目:code15-026】使用MethodInvoker和Invoke跨线程设置UI
Int64 allsum;
private void button1_Click(object sender, EventArgs e)
{
Thread th = new Thread(getSum);
th.Start();
}
private void getSum()
{
Int64 sum = 0;
for (Int64 i = 1; i <= 1000000000; i++)
sum += i;
allsum = sum;
MethodInvoker showSum = showsumIntext;
textBox1.Invoke(showSum);
}
//无参数,无返回值的方法
private void showsumIntext()
{
textBox1.Text = allsum.ToString();
}
运行结果同【例 15.24】。
学习更多vb.net知识,请参看vb.net 教程 目录
学习更多C#知识,请参看C#教程 目录