某个线程上创建的控件不能成为在另一个线程上创建的控件的父级(转)

本文介绍如何在.NET Framework中正确地使用多线程来更新Windows窗体控件,避免因跨线程操作引发的异常。通过具体示例演示了如何使用Invoke方法确保控件始终在其创建线程上进行更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

解决多线程操作控件时可能出现的异常:“在某个线程上创建的控件不能成为在另一个线程上创建的控件的父级”
Posted on 2005-10-25 14:47 HQT 阅读(218) 评论(0)   编辑  收藏 收藏至365Key 所属分类: .NET
.NET Framework 中关于“多线程 Windows 窗体控件示例”的一些摘要:

------------------------------------------------------

“Windows 窗体”使用单线程单元 (STA) 模型,因为“Windows 窗体”基于本机 Win32 窗口,而 Win32 窗口从本质上而言是单元线程。STA 模型意味着可以在任何线程上创建窗口,但窗口一旦创建后就不能切换线程,并且对它的所有函数调用都必须在其创建线程上发生。除了 Windows 窗体之外,.NET Framework 中的类使用自由线程模型。有关 .NET Framework 中的线程的信息,请参见线程处理

STA 模型要求需从控件的非创建线程调用的控件上的任何方法必须被封送到(在其上执行)该控件的创建线程。基类 Control 为此目的提供了若干方法( Invoke 、 BeginInvoke 和 EndInvoke )。Invoke 生成同步方法调用;BeginInvoke 生成异步方法调用。

如果您在控件中为大量占用资源的任务使用多线程,则用户界面可以在背景线程上执行一个大量占用资源的计算的同时保持可响应。

------------------------------------------------------

下面直接给出我的实例程序,一个按钮是非创建线程直接调用 DataGrid 控件的数据绑定,将抛出异常;
另一个按钮是通过 Invoke 调用,将成功执行。

using  System;
using  System.Data;
using  System.Threading;
using  System.Drawing;
using  System.Collections;
using  System.ComponentModel;
using  System.Windows.Forms;

namespace  MultiThreadOperateControls
{
    
/// <summary>
    
/// Form1 的摘要说明。
    
/// </summary>

    public class Form1 : System.Windows.Forms.Form
    
{
        
        
private delegate void BindDataGridDelegate();        // 创建委托和委托对象
        private BindDataGridDelegate bindDataGridDelegate;

        Thread bindGridThread;

        
private System.Windows.Forms.Button btnErrorHandle;
        
private System.Windows.Forms.Button btnSuccessHandle;
        
private System.Windows.Forms.DataGrid dataGrid1;
        
/// <summary>
        
/// 必需的设计器变量。
        
/// </summary>

        private System.ComponentModel.Container components = null;

        
public Form1()
        
{
            bindDataGridDelegate 
= new BindDataGridDelegate(BindDataGrid);    // 实例化委托对象并指定调用的方法

            
//
            
// Windows 窗体设计器支持所必需的
            
//
            InitializeComponent();

            
//
            
// TODO: 在 InitializeComponent 调用后添加任何构造函数代码
            
//
        }


        
/// <summary>
        
/// 清理所有正在使用的资源。
        
/// </summary>

        protected override void Dispose( bool disposing )
        
{
            
if( disposing )
            
{
                
if (components != null
                
{
                    components.Dispose();
                }

            }

            
base.Dispose( disposing );
        }


        
Windows 窗体设计器生成的代码

        
/// <summary>
        
/// 应用程序的主入口点。
        
/// </summary>

        [STAThread]
        
static void Main() 
        
{
            Application.Run(
new Form1());
        }


        
private void Form1_Load(object sender, System.EventArgs e)
        
{
            BindDataGrid();
        }


        
private void btnErrorHandle_Click(object sender, System.EventArgs e)
        
{
            StopBindThread();
            bindGridThread 
= new Thread(new ThreadStart(BindDataGrid));    // 直接调用非此线程创建的控件的操作 抛出异常
            bindGridThread.Start();        
        }

        
private void btnSuccessHandle_Click(object sender, System.EventArgs e)
        
{            
            StopBindThread();
            bindGridThread 
= new Thread(new ThreadStart(InvokeBindDataGrid)); // 通过委托调用,合法
            bindGridThread.Start();                
        }


        
private void BindDataGrid()
        
{
            
int dataGridItemLenght = 5;
            DataTable dt 
= new DataTable("DataGridSource");
            dt.Columns.Add(
"RandomValue");
            
for(int i=0;i<dataGridItemLenght;i++)
            
{
                Random r 
= new Random();
                DataRow dr 
= dt.NewRow();
                dr[
0= r.Next(1,500);
                dt.Rows.Add(dr);
            }


            
try
            
{
                dataGrid1.DataSource 
= dt;
            }

            
catch(Exception ex)
            
{
                MessageBox.Show(ex.Message);
            }

        }

        
        
private void InvokeBindDataGrid()        // 调用非此线程创建的控件的操作必须用 Invoke 或 BeginInvoke .否则将抛出异常
        {        
            dataGrid1.Invoke(bindDataGridDelegate,
null);            
        }

        
private void StopBindThread()
        
{
            
if((bindGridThread != null)&&(bindGridThread.IsAlive))
            
{
                bindGridThread.Abort();
                bindGridThread.Join();
            }

            bindGridThread 
= null;
        }

    }

}

### C# 中解决跨线程访问控件引发 `System.ArgumentException` 的方法 在多线程应用程序中,尝试从非创建线程更新 Windows Forms 控件的内容会抛出异常。这是因为 .NET Framework 对于 GUI 控件有严格的线程亲和性要求[^1]。 当遇到此类问题时,可以使用 `Control.InvokeRequired` 属性来判断当前调用是否来自不同的线程,并通过 `Invoke` 或 `BeginInvoke` 方法将操作委派给正确的线程执行。以下是具体的解决方案: #### 判断并安全地更新控件内容 可以通过以下方式实现线程安全的操作: ```csharp private void UpdateLabelText(string newText) { if (someLabel.InvokeRequired) // 如果当前线程不是UI线程,则需要调用Invoke { someLabel.Invoke((MethodInvoker)(() => someLabel.Text = newText)); } else // 否则可以直接修改控件属性 { someLabel.Text = newText; } } ``` 上述代码片段展示了如何检测是否需要调用 `Invoke` 来确保线程安全性。如果 `InvokeRequired` 返回 true,则表示当前线程无法直接访问该控件,因此必须通过委托的方式切换到 UI 线程上完成操作。 #### 使用异步模式处理复杂场景 对于更复杂的场景或者涉及长时间运行的任务,推荐采用 Task 并配合同步上下文的方法来进行交互。例如: ```csharp Task.Run(() => { string result = PerformLongRunningOperation(); // 假设这是一个耗时操作 this.BeginInvoke(new Action(() => { someLabel.Text = $"Result: {result}"; })); }); ``` 这里利用了 `Task.Run()` 将工作卸载至后台线程线程,在完成后通过 `BeginInvoke` 更新界面状态而不会阻塞主线程响应用户输入[^2]。 #### 防止竞争条件的发生 需要注意的是,即使采用了 `Invoke`/`BeginInvoke` 方式也可能存在潜在的竞争情况。为了进一步减少风险,应尽量避免共享可变数据结构之间的冲突;同时合理设计程序逻辑使得每次只允许单一路径去改变特定资源的状态。 --- ### 总结 综上所述,针对因跨线程访问引起的 `System.ArgumentException` 错误,最有效的办法就是借助 WinForms 提供的 `Control.InvokeRequired` 和其关联方法(如 `Invoke`, `BeginInvoke`),从而保障所有对图形化组件实例成员函数或字段赋值的行为都发生在它们所属的那个唯一合法的工作进程中。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值