我知道这个问题已被问过几次,但我似乎无法找到为什么在我的情况下这样做。
首先,我会解释一下我的程序。它通过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主题。为了解释这种情况:我有一个需要保持活动的连接,所以我有一个线程运行每500ms来向我的设备发送一个特殊的标题。这是我保持活动的线程代码:
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,因此保持活动线程在下一个500ms的休眠期后终止。
我将保持活动的线程更改为后台工作者。工作良好。但是我没有得到线程和后台工作者之间的区别,在这种情况下不应该两者都一样吗?
答案 0 :(得分:4)
儿童表格中是否有任何线程?如果是这样,这是我的理论:
您可能会看到的是竞争条件,其中第一次显示客户端时,客户端表单正忙于连接到后台线程上的某个设备,而在此期间您的MDI父UI线程显示()s形式(因此拥有窗口句柄,一切都很好)。第二次显示客户端时,会得到一个缓存连接,因此子进程中的后台线程非常快速地连接,然后调用一些UI操作,可能会像使用InvokeRequired()的优秀开发人员一样进行检查。由于您的客户端表单还没有句柄,后台线程对于InvokeRequired变为false,然后调用并创建句柄本身。
所有这些都记录在Ivan Krivyakov的great post中。
因此,如果以上所有声音都正确,则只需在子窗体中启动后台工作,直到创建句柄。您可能希望将其挂在Form Shown event而不是构造函数上。