[转]线程间操作无效: 从不是创建控件“...”的线程访问它。

本文介绍在Windows窗体应用中如何确保线程安全地访问控件,避免因多线程操作导致的异常状态及bug。演示了非线程安全与线程安全设置TextBox文本的方法。

访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。

.NET Framework 有助于在以非线程安全方式访问控件时检测到这一问题。在调试器中运行应用程序时,如果创建某控件的线程之外的其他线程试图调用该控件,则调试器会引发一个 InvalidOperationException,并提示消息:“从不是创建控件 control name 的线程访问它。”

此异常在调试期间和运行时的某些情况下可靠地发生。强烈建议您在显示此错误信息时修复此问题。在调试以 .NET Framework 2.0 版之前的 .NET Framework 编写的应用程序时,可能会出现此异常。
注意
可以通过将 CheckForIllegalCrossThreadCalls 属性的值设置为 false 来禁用此异常。这会使控件以与在 Visual Studio 2003 下相同的方式运行。

下面的代码示例演示如何从辅助线程以线程安全方式和非线程安全方式调用 Windows 窗体控件。它演示一种以非线程安全方式设置 TextBox 控件的 Text 属性的方法,还演示两种以线程安全方式设置 Text 属性的方法。


转自:http://www.cnblogs.com/imissherso/archive/2007/01/28/632480.html

<think> 我们正在处理一个WinForms应用程序中常见的跨线程访问控件的问题。错误信息“WinForm线程间操作无效: 从不创建控件‘txtIncomingMap’的线程访问它”表明,我们尝试在一个非UI线程(即不是创建控件线程)中修改控件“txtIncomingMap”的状态。 在WinForms中,控件只能由创建它的线程(通常是主UI线程)进行访问和修改。如果从其他线程直接访问控件,就会引发此类异常。 解决方案通常有两种: 1. **使用Control.Invoke或Control.BeginInvoke方法**:这些方法允许我们在UI线程上执行委托,从而安全地更新控件。 2. **使用BackgroundWorker组件**:它封装了跨线程调用的细节,通过事件机制在UI线程上报告进度。 下面是一个使用Invoke方法的示例: ```csharp // 假设我们在一个非UI线程中,想要更新名为txtIncomingMap的文本框 if (txtIncomingMap.InvokeRequired) { // 如果当前线程不是创建控件线程,则通过Invoke方法调用委托 txtIncomingMap.Invoke(new Action(() => { txtIncomingMap.Text = "更新后的文本"; })); } else { // 如果当前线程就是UI线程,则直接更新 txtIncomingMap.Text = "更新后的文本"; } ``` 另外,我们也可以使用BackgroundWorker来简化操作: ```csharp // 初始化BackgroundWorker BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += (s, e) => { // 在后台线程中执行耗时操作 // 注意:不要在这里直接更新UI }; worker.RunWorkerCompleted += (s, e) => { // 此事件在UI线程中触发,可以安全更新UI txtIncomingMap.Text = "操作完成后的文本"; }; worker.RunWorkerAsync(); ``` 此外,从.NET Framework 2.0开始,还可以使用异步模式(如async/await)结合Task.Run来执行后台任务,然后在需要更新UI时通过调度回UI线程: ```csharp // 在事件处理函数中 private async void button1_Click(object sender, EventArgs e) { // 在后台线程执行耗时操作 await Task.Run(() => { // 模拟耗时操作 System.Threading.Thread.Sleep(1000); }); // 回到UI线程,可以安全更新控件 txtIncomingMap.Text = "更新后的文本"; } ``` 但注意:在异步方法中,await之后的代码默认会回到原来的上下文(对于UI线程来说,就是UI线程),所以可以直接更新控件。 为了避免此类问题,我们应该始终确保对控件的更新操作在UI线程上执行。 §§相关问题§§ 1. WinForm中InvokeRequired属性有什么作用?请举例说明其使用场景。 2. 除了使用Invoke/BeginInvoke,还有哪些方法可以安全地在多线程中更新WinForm控件? 3. 在WPF中,跨线程更新UI元素的方法与WinForms有什么不同? 4. 如何避免在多线程编程中频繁使用Invoke导致界面卡顿? 5. 使用BackgroundWorker组件时,如何报告进度并更新UI? </think> 在WinForms应用程序中出现“线程间操作无效: 从不创建控件‘txtIncomingMap’的线程访问它”错误,是因为尝试在非UI线程(后台线程)中直接操作UI控件。WinForms要求所有对控件访问必须发生在创建控件线程(主UI线程)上。以下是解决方案: ### 解决方法 1. **使用`Control.Invoke`方法**(同步调用): ```csharp if (txtIncomingMap.InvokeRequired) { txtIncomingMap.Invoke(new Action(() => { // 在此安全更新控件 txtIncomingMap.Text = "新内容"; })); } else { txtIncomingMap.Text = "新内容"; // 当前已是UI线程时直接操作 } ``` 2. **使用`Control.BeginInvoke`方法**(异步调用): ```csharp txtIncomingMap.BeginInvoke(new Action(() => { // 异步更新控件 txtIncomingMap.AppendText("异步添加的内容"); })); ``` 3. **使用`BackgroundWorker`组件**(推荐): ```csharp BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += (s, e) => { // 后台线程执行耗时操作 }; worker.RunWorkerCompleted += (s, e) => { // 此事件在UI线程触发,可安全更新控件 txtIncomingMap.Text = "操作完成"; }; worker.RunWorkerAsync(); ``` 4. **使用`async/await`模式**(.NET 4.5+): ```csharp private async void ProcessDataAsync() { var result = await Task.Run(() => { // 后台处理 return "处理结果"; }); // 自动回到UI线程 txtIncomingMap.Text = result; } ``` ### 关键注意事项 - 所有UI操作必须通过`Invoke`/`BeginInvoke`或事件回调机制切换到UI线程 - 避免在后台线程中直接访问控件的任何属性或方法 - 使用`InvokeRequired`检查当前是否在UI线程 - 优先考虑`BackgroundWorker`或`async/await`等更安全的模式
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值