一、问题原因
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 falsethis.Invoke(new Action(() =>{richTextBox_Receive.AppendText($"[{DateTime.Now}] {msg}\n"); //在日志窗口显示消息}));
#elif falseAction action = () =>{richTextBox_Receive.AppendText($"[{DateTime.Now}] {msg}\n"); //在日志窗口显示消息};Invoke(action);
#elseif (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";}
}
参考
- 通过 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
- 参考1
- 参考2