1

我花了一些时间寻找这个问题的答案,并在其他线程中找到了大量有用的信息。我相信我已经以一种有效的方式编写了代码,但我对结果并不满意。

我设计了一个通过 C# 与之通信的硬件。硬件通过 USB 连接并在与操作系统枚举后运行初始化例程。此时,它只是等待 C# 程序开始发送命令。在我的 C# 代码中,用户必须按下“连接”按钮,该按钮会发送命令和所需的有效负载,让硬件知道它应该继续运行。然后硬件将命令作为 ACK 发回。问题是我的 C# 程序必须等待接收 ACK,但 GUI 完全冻结,直到硬件响应,因为我不知道如何将其分区到另一个可以自由阻塞的线程。如果硬件立即响应,那么它工作正常,但如果它无法连接,那么程序会无限期地保持冻结状态。

话虽如此,我知道需要发生一些事情,但我不确定如何实现它们。首先,我不认为坐在循环中等待布尔值是正确的方法,但使用 AutoResetEvent 似乎并没有好得多。必须有更好的方法涉及计时器、更多线程或类似的东西。

我将 DataReceived 事件与 serialPort 对象一起使用,如下所示:

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    byte cmd = (byte)serialPort1.ReadByte();

    if (cmd == (byte)Commands.USB_UART_CMD_MCU_CONNECT)
        MCU_Connect_Received.Set();

}

在 buttonClick 函数(“主”线程)中,程序在等待 ACK 时停止:

//Send the command to signal a connection
Send_Connection_Packet((byte)Commands.USB_UART_CMD_PC_CONNECT);

textBox1.AppendText("-I- Attempting to contact hardware...");

MCU_Connect_Received.WaitOne();

textBox1.AppendText("Success!" + Environment.NewLine);

理想情况下,我想知道超时是否过期,以便打印“失败!” 而不是“成功!”。正如我上面提到的,没有超时也意味着它将永远坐在那里,直到我终止进程。它可能找不到任何硬件,但如果找到了,它应该会在 < 1 秒内响应,因此 2 秒的超时就足够了。我尝试使用 Thread.Sleep,但这也冻结了 GUI。

4

4 回答 4

4

我建议你使用这个Task类。操作完成后,您可以使用 aTaskCompletionSource来完成任务。

使用新的async support,您的代码将变为:

textBox1.AppendText("-I- Attempting to contact hardware...");
await Send_Connection_Packet((byte)Commands.USB_UART_CMD_PC_CONNECT);
textBox1.AppendText("Success!" + Environment.NewLine); 

如果您不想使用 Async CTP,那么您可以调用Task.ContinueWith并传递TaskScheduler.FromCurrentSynchronizationContext以安排该textBox1.AppendText("Success!")行在 UI 线程上运行。

异步支持还包括计时器 ( TaskEx.Delay) 和组合器 ( TaskEx.WhenAny),因此您可以轻松检查超时:

textBox1.AppendText("-I- Attempting to contact hardware...");
var commTask = Send_Connection_Packet((byte)Commands.USB_UART_CMD_PC_CONNECT);
var timeoutTask = TaskEx.Delay(1000);
var completedTask = TaskEx.WhenAny(commTask, timeoutTask);
if (completedTask == commTask)
  textBox1.AppendText("Success!" + Environment.NewLine); 
else
  textBox1.AppendText("Timeout :(" + Environment.NewLine);
于 2012-01-08T04:06:37.310 回答
2

GUI 冻结的问题是因为 GUI 事件的所有回调都发生在运行 GUI 的线程中。如果您不希望 GUI 冻结,您需要生成一个新线程

为了实现超时,您可以对事件句柄进行定时等待,然后检查返回值truefalse确定调用是否成功或是否超时。

于 2012-01-08T04:05:13.537 回答
2

要启用超时,请使用 WaitOne() 的另一个重载:

 bool succeeded = MCU_Connect_Received.WaitOne(timeOutInMilliseconds, false);
 if (succeeded)  
 {
       textBox1.AppendText("Success!" + Environment.NewLine);         
 }
 else
 {
       textBox1.AppendText("Failed!" + Environment.NewLine);         
 }

考虑将与通信相关的代码移动到单独的类中以封装通信协议。这样代码将更容易维护,您将能够实现其他人建议的所有任务/后台工作人员的想法。

于 2012-01-08T04:35:19.517 回答
1

如果您希望 GUI 保持响应,您应该在后台线程中运行。BackgroundWorker可以很好地做到这一点。在繁忙的等待结构中,我会坚持使用 resetevent。您可以使用计时器在超时时间后触发 resetevent

于 2012-01-08T04:06:21.407 回答