跨线程操作无效:控件从创建它的线程以外的线程访问
Posted
技术标签:
【中文标题】跨线程操作无效:控件从创建它的线程以外的线程访问【英文标题】:Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on 【发布时间】:2012-02-17 00:27:28 【问题描述】:我知道这个问题已经被问过好几次了,但我似乎不太明白为什么在我的情况下会这样。
首先,我将解释一下我的程序。它通过 FTDI 芯片连接到硬件设备,因此它通过 USB 为我们生成 COM。我的程序启动,它是一个 MDI 接口。单击“连接”会出现一个类似于 Windows 中的“添加设备”框的连接框。它扫描计算机上的所有 COM 并尝试连接到它,以报告它是什么类型的设备。之后,用户点击一个设备,连接到它,然后打开一个子窗体来控制该设备。
所以,我的问题是,我有很多多线程在里面进行。我第一次连接到我的设备时,它工作正常。第二次返回跨线程操作错误。
这是我的代码的一个简短示例:
private void ConnectToolStripButton_Click(object sender, EventArgs e)
Dialogs.Connect Connect = new Dialogs.Connect();
if (Connect.ShowDialog() == DialogResult.OK)
this.Connect(Connect.Connection);
private void Connect(CommunicationInterfaces.Base Connection)
// Set the connection to the one the connect dialog gave us.
Child NewConnection = new Child(Connection);
// Set the parent of the new child and show it.
NewConnection.MdiParent = this;
NewConnection.Show(); // CRASH HERE!
所以它在 .show() 上崩溃并出现以下错误,但仅在我第二次连接到它时:Cross-thread operation not valid: Control 'Child' accessed from a thread other than the thread it was created on.
如果我没记错的话,就是在 UI 线程上创建了 Child(我的子窗体的名称)对象。那为什么会给我一个跨线程操作错误呢?这是我的孩子形式的问题吗?
更新:保持活动计时器
所以我已经能够更多地指出问题所在。问题在于我的孩子形式的 Keep Alive 线程。为了解释这种情况:我有一个需要保持活动的连接,所以我有一个线程每 500 毫秒运行一次,以向我的设备发送一个特殊的标头。这是我的保持活动线程代码:
private void Child_Shown(object sender, EventArgs e)
this.Connection.DataReceived += DisplayData;
...
private void DisplayData(object Sender, byte[] Data)
...
CreateFaultBox((FaultBoxes.Base.BoxTypes)Data[1]);
...
private void CreateFaultBox(FaultBoxes.Base.BoxTypes BoxType)
KeepAliveTimer = new System.Threading.Thread(new System.Threading.ThreadStart(this.KeepAlive));
KeepAliveSwitch = true;
KeepAliveTimer.Start();
...
private void KeepAlive()
while (Connection != null && KeepAliveSwitch)
Console.WriteLine("KEEP ALIVE");
// Keep the connection alive.
Connection.KeepAlive();
// Wait 500ms for the next keep alive.
System.Threading.Thread.Sleep(500);
如果我删除前 3 行,那么如果我不启动线程,它就可以正常工作。当然,当我关闭表单时,KeepAliveSwitch 设置为 false,因此在接下来的 500 毫秒睡眠期后,保持活动线程会终止。
解决方案
我将保持活动线程更改为后台工作线程。工作正常。但是我不明白线程和后台工作人员之间的区别,在这种情况下两者不应该相同吗?
【问题讨论】:
【参考方案1】:子表单中是否有任何线程?如果是这样,这就是我的理论:
您可能会看到一个竞争条件,其中您第一次显示客户端时,客户端表单正忙于连接到后台线程上的某个设备,而与此同时您的 MDI 父 UI 线程 Show()s 子形式(因此拥有窗口句柄,一切都很好)。第二次显示客户端时,您获得了一个缓存连接,因此子进程中的后台线程非常快速地连接,然后调用一些 UI 操作,可能像使用 InvokeRequired() 的优秀开发人员一样进行检查。由于您的客户端表单还没有句柄,因此后台线程将 InvokeRequired 设为 false,然后调用并自行创建句柄。
所有这些都记录在 Ivan Krivyakov 的great post 中。
因此,如果以上所有内容都正确,那么在创建句柄之前不要在 Child 表单中启动后台工作。您可能希望将其挂在 Form Shown event 而不是构造函数上。
【讨论】:
这正是问题所在。在您的帮助下,我设法将它指向我的 DisplayData 事件(连接事件),我正在将处理程序添加到我孩子的构造函数中的事件中。但是,即使我在 Child 的 Shown 事件中添加了处理程序,它仍然给我带来了完全相同的问题。如果我删除事件处理程序,它会正常工作。我将尝试在我的活动中指出问题。我尝试添加 InvokeRequired 检查(因为我没有进行检查),但仍然不行。我会及时通知你的。非常感谢! 如果您添加该事件处理程序的代码并在哪里挂钩,我可能会提供进一步的帮助。 我已经添加了所有代码。哎呀,这个程序越来越大了!哈哈 每次从连接接收到任何数据时,您似乎都在启动一个新的保活线程。这是故意的吗? 只有在我第一次收到数据时,我才删除了 if。但我通过切换到后台工作人员使其工作,但并不真正理解其中的区别。以上是关于跨线程操作无效:控件从创建它的线程以外的线程访问的主要内容,如果未能解决你的问题,请参考以下文章
跨线程操作无效:控件“chart1”从创建它的线程以外的线程访问
跨线程操作无效:控件'listBox1'从一个>线程访问,而不是它在[重复]上创建的线程