c# 线程间操作无效—从不是创建控件的线程访问它,解决办法

本文介绍了C#中线程间操作无效错误的原因,主要讨论了三种解决方案:禁用线程间操作检查、使用Invoke或BeginInvoke方法以及利用BackgroundWorker。重点讲解了如何在多线程环境下确保UI控件的正确访问和操作。

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

一、问题原因

  C#中的线程间操作无效错误通常是由于在非创建控件的线程上访问控件引发的这是因为UI控件只能在创建它们的线程上进行访问和操作,否则会引发异常。
  问题的根源是在多线程应用程序中,当一个线程尝试访问或修改UI控件时,如果该线程不是创建控件的线程,就会引发线程间操作无效错误。

二、解决办法

1、方法1,不检查线程间操作

不检查线程间操作

CheckForIllegalCrossThreadCalls = false;      //不检查线程间操作(如果程序抛出了“线程间操作无效”异常,可以添加该行代码)
 public KUKA_TCP_SERVER()
 {
     InitializeComponent();
 
     CheckForIllegalCrossThreadCalls = false;
 }

2、方法2,使用Invoke方法

  解决这个问题的一种常见方法是使用Invoke或BeginInvoke方法来将操作委托给创建控件的线程执行。这样可以确保UI控件的访问和操作在正确的线程上进行。

Thread txThread = new Thread(() =>
{
    writeMessage("haha");
});
txThread.Start();

private void writeMessage(string text)
{
    if (textBox1.InvokeRequired)
    {
        Action<string> action = new Action<string>(SetText);
        Invoke(action, new object[] { text });
    }
    else
    {
        textBox1.Text = text;
    }
}
 /// <summary>
        /// TCP放在后台线程
        /// </summary>
        private void OpenTCP()
        {
            ThreadStart TCPDelegate = new ThreadStart(TCPListen);   //新建一个委托线程
            tcpDelegate = new Thread(TCPDelegate);                  //实例化新线程
            tcpDelegate.Start();
        }

        /// <summary>
        /// 创建TCPServer并监听
        /// </summary>
        public void TCPListen()
        {
            var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);     //初始化 Socket 实例,该 Socket 只用于绑定本地终结点并接受客户端连接
            listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);        //允许 Socket 被绑定在已使用的地址上
            try
            {
                localEP = new IPEndPoint(IPAddress.Parse(ipaddress), setPort);                              //初始化本地终结点实例
                listener.Bind(localEP);                //绑定
                listener.Listen(10);                   //监听
                Print("开始监听");
                listener.BeginAccept(new AsyncCallback(OnConnectRequest), listener);                        //开始接受异步连接
                /** 
                 * BeginAccept 方法的第二个参数(object state)是一个用户定义的对象,它可以传递给回调函数,该参数可以用于在回调函数中传递额外的信息。
                 * 比如我们将监听 Socket 对象作为 state 参数传递给了 BeginAccept 方法,在 BeginAccept 方法中,它会使用监听 Socket 对象去初始化一个 IAsyncResult 对象并返回。
                 * IAsyncResult 对象包含了一个 AsyncState 属性,它的值就是传递给 BeginAccept 方法的第二个参数 state。这样,我们就能够在回调函数中使用该属性来获取监听 Socket 对象,以便继续监听客户端连接。
                 * 当然,可以根据需要传递任何类型的对象作为 state 参数。这个参数对于在回调函数中传递额外的信息非常有用。
                 */
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
         private void Print(string msg)
        {
            //获取句柄再向下继续执行,以确保 Invoke() 方法执行不会出现异常。
            //IntPtr handlePtr = this.Handle;//出现异常
            /**
             * 通过 Invoke() 方法执行特定委托以避免“线程间操作无效”,请参考:<https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.forms.control.invoke?view=windowsdesktop-8.0>
             * 有关 Action() 强类型委托的相关内容,请参考:<https://learn.microsoft.com/zh-cn/dotnet/csharp/delegates-strongly-typed>,
             * 有关 Lambda 表达式语法,请参考:<https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/lambda-expressions>
             */
#if false
            this.Invoke(new Action(() =>
            {
                richTextBox_Receive.AppendText($"[{DateTime.Now}] {msg}\n");  //在日志窗口显示消息
            }));
#elif false
            Action action = () =>
            {
                richTextBox_Receive.AppendText($"[{DateTime.Now}] {msg}\n");  //在日志窗口显示消息
            };
            Invoke(action); 
#else
            if (richTextBox_Receive.InvokeRequired)
            {
                this.Invoke(new Action(() =>
                {
                    richTextBox_Receive.AppendText($"[{DateTime.Now}] {msg}\n");  //在日志窗口显示消息
                }));
            }
            else
            {
                richTextBox_Receive.AppendText($"[{DateTime.Now}] {msg}\n");  //在日志窗口显示消息
            }
#endif
        }

3、方法3,使用BackgroundWorker和Invoke

BackgroundWorker backgroundWorkerText = new BackgroundWorker();
backgroundWorkerText.RunWorkerCompleted += backgroundWorkerText_RunWorkerCompleted;

backgroundWorkerText.RunWorkerAsync();

private void backgroundWorkerText_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
 {
            
            if (textBox1.InvokeRequired)
            {
                this.Invoke(new Action(() =>
                {
                    textBox1.Text = "haha";
                }));
            }
            else
            {
                textBox1.Text = "haha";
            }
}

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值