实现Winform 跨线程安全访问UI控件
Posted asdyzh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实现Winform 跨线程安全访问UI控件相关的知识,希望对你有一定的参考价值。
在多线程操作WinForm窗体上的控件时,出现“线程间操作无效:从不是创建控件XXXX的线程访问它”,那是因为默认情况下,在Windows应用程序中,.NET Framework不允许在一个线程中直接操作另一个线程中的控件(因为访问Windows窗体控件本质上不是线程安全的)。微软为了线程安全,窗体上的控件只能通过创建控件的线程来操作控件的数据,也就是只能是UI线程来操作窗体上的控件!可看看Control的Invoke和BeginInvoke
要解决这个问题可以用以下方法:
1、关闭线程安全检查(不过本人不推荐,这种方式可能会发生一些不可预计的后果)
Control对象.CheckForIllegalCrossThreadCalls = false;
2、使用控件的Invoke方法(或BeginInvoke方法、BackgroundWorker)
(1)、Invoke方法
if (this.InvokeRequired) { this.Invoke(new Action(() => button1.Enabled = false)); //button1.Invoke(new MethodInvoker(delegate() { button1.Enabled = false; }));
//textBox1.SafeCall(() =>{ textBox1.Text = (i++).ToString();});
button1.Invoke(new MethodInvoker(() => button1.Enabled = false )); button1.Invoke(new Action(() => button1.Enabled = false)); // 跨线程访问UI控件 } else { button1.Enabled = false }
(2)、BeginInvoke方法
delegate void AppendValueDelegate(string strValue); public void AppendValue(string strValue) { textBox1.BeginInvoke(new AppendValueDelegate(AddValueToTextBox), new object[] { strValue }); } private void AddValueToTextBox(string strValue) { textBox1.Text += strValue; }
可精简成:
public void AppendValue(string strValue) { // 无返回值无参数用MethodInvoker委托,无返回值可有参数用Action委托,有返回值可有参数用Func委托 textBox1.BeginInvoke(new Action<string>(msg => textBox1.Text += msg), new object[] { strValue }); }
3、使用委托
public delegate void AddLog(string info); /// <summary> /// 添加日志 /// </summary> AddLog OnAddLog; /// <summary> /// 加载事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void FrmDataBackup_Load(object sender, EventArgs e) { OnAddLog = new AddLog(PrintMsg); } /// <summary> /// 打印信息到即时显示控件 /// </summary> /// <param name="info"></param> public void PrintMsg(string info) { // InvokeRequired 属性判断是否跨线程操作 if (this.InvokeRequired) { this.Invoke(OnAddLog, info); return; } listBoxInfo.Items.Insert(0, "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] " + info); if (listBoxInfo.Items.Count > 100) { listBoxInfo.Items.RemoveAt(100); } }
4、使用SynchronizationContext基类,该类记录着线程的同步上下文对象,我们可以通过在GUI线程中调用SynchronizationContext.Current属性来获得GUI线程的同步上下文,然后当线程池线程需要更新窗体时,可以调用保存的SynchronizationContext派生对象的Post方法(Post方法会将回调函数送到GUI线程的队列中,每个线程都有各自的操作队列的,线程的执行都是从这个队列中拿方法去执行),向Post方法传递要由GUI线程调用的方法(该方法的定义要匹配SendOrPostCallback委托的签名),还需要想Post方法传递一个要传给回调方法的参数。
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.Runtime.Remoting.Messaging; using System.Threading; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } /// <summary> /// 定义用来实现异步编程的委托 /// </summary> /// <returns></returns> private delegate string GetTextInfoDelegate(); /// <summary> /// 线程同步上下文对象 /// </summary> SynchronizationContext syncContext; /// <summary> /// 调用委托 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button1_Click(object sender, EventArgs e) { GetTextInfoDelegate textInfo = new GetTextInfoDelegate(GetInfo); textInfo.BeginInvoke(new AsyncCallback(GetTextInfoResult), null); // 捕捉调用线程的同步上下文派生对象 syncContext = SynchronizationContext.Current; } /// <summary> /// 异步操作获取信息 /// </summary> /// <returns></returns> private string GetInfo() { return "使用SynchronizationContext基类"; } /// <summary> /// 异步操作完成时执行的方法 /// </summary> /// <param name="result"></param> private void GetTextInfoResult(IAsyncResult result) { GetTextInfoDelegate caller = (GetTextInfoDelegate)((AsyncResult)result).AsyncDelegate; // 调用EndInvoke去等待异步调用完成并且获得返回值 // 如果异步调用尚未完成,则 EndInvoke 会一直阻止调用线程,直到异步调用完成 string text = caller.EndInvoke(result); // 通过获得GUI线程的同步上下文的派生对象, // 然后调用Post方法来使更新GUI操作方法由GUI 线程去执行 // ShowText(text); // 报错:线程间操作无效: 从不是创建控件“textBox1”的线程访问它。 syncContext.Post(new SendOrPostCallback(ShowText), text); } /// <summary> /// 显示结果到TextBox文本框 /// 因为该方法是由GUI线程执行的,所以当然就可以访问窗体控件了 /// </summary> /// <param name="text"></param> private void ShowText(object text) { textBox1.Text = text.ToString(); } } }
参考资料:http://www.cnblogs.com/easyfrog/archive/2012/02/08/2343075.html 和 http://www.cnblogs.com/zhili/archive/2013/05/10/APM.html
以上是关于实现Winform 跨线程安全访问UI控件的主要内容,如果未能解决你的问题,请参考以下文章