一、问题描述
进行winform 开发我们在进行数据交换时避免不了使用多线程或异步方法,这样操作也将避免不了跨线程对控件进行操作(赋值、修改属性)。
下面通过一个测试说明一下问题
点击一个按钮异步对textbox进行赋值
运行测试结果
1 private void button1_Click(object sender, EventArgs e) 2 { 3 Action<string> action = new Action<string>((str) => 4 { 5 // 解决跨线程赋值 6 this.textBox1.Text = str; 7 }); 8 action.BeginInvoke("Test", null, null); 9 }
产生错误的原因:textBox1是由主线程创建的,异步方法(或子线程)是另外创建的一个线程,在.NET上执行的是托管代码,C#强制要求这些代码必须是线程安全的,即不允许跨线程访问Windows窗体的控件。
二、解决方法
1.在窗体的加载事件中,将C#内置控件(Control)类的CheckForIllegalCrossThreadCalls属性设置为false,屏蔽掉C#编译器对跨线程调用的检查。
1 //取消跨线程的访问 2 Control.CheckForIllegalCrossThreadCalls = false;
使用上述的方法虽然可以保证程序正常运行并实现应用的功能,但是在实际的软件开发中,做如此设置是不安全的(不符合.NET的安全规范),在产品软件的开发中,此类情况是不允许的。如果要在遵守.NET安全标准的前提下,实现从一个线程成功地访问另一个线程创建的空间,要使用C#的方法回调机制。
2.使用回调函数 ( 委托的应用 )
(1) 定义声明回调
(2) 初始化回调
(3) 触发动作
1 /// <summary> 2 /// 定义委托 3 /// </summary> 4 /// <param name="str"></param> 5 private delegate void SetValueDelegate(string str); 6 7 /// <summary> 8 /// 声明委托 9 /// </summary> 10 SetValueDelegate test; 11 12 /// <summary> 13 /// 回调方法 14 /// </summary> 15 /// <param name="str"></param> 16 private void SetTbValue(string str) 17 { 18 this.textBox1.Text = str; 19 } 20 21 private void button1_Click(object sender, EventArgs e) 22 { 23 // 实例化委托 24 test = new SetValueDelegate(SetTbValue); 25 26 Action<string> action = new Action<string>((str) => 27 { 28 // 解决跨线程赋值 29 this.textBox1.Invoke(test, str); 30 }); 31 32 action.BeginInvoke("Test", null, null); 33 }
我个人比较使用喜欢使用事件解决(原理同委托一样),但省掉不少代码
1 private void button1_Click(object sender, EventArgs e) 2 { 3 Action<string> action = new Action<string>((str) => 4 { 5 // 解决跨线程赋值 6 this.textBox1.Invoke( 7 new Action<string>((param) => 8 { 9 this.textBox1.Text = param; 10 } 11 ), str); 12 }); 13 14 action.BeginInvoke("Test", null, null); 15 }