QSerialPort :: waitForBytesWritten"微妙的错误"有一个事件循环?

时间:2016-05-27 17:25:23

标签: c++ qt serial-port debian

an answer on another question中,它声称当使用带有QSerialPort的主要Qt事件循环时,QSerialPort::waitForBytesWritten会导致"微妙的错误"因为它会打开另一个事件循环。

我使用waitForBytesWritten方法确保在写入所有字节之前不会重新输入主事件循环。这不是一种有效的方法吗?什么"微妙的错误"会发生什么?我应该使用flush()代替,并确保我的事件循环不再再次输入,直到所有未决字节都被写入?

在我的应用程序中看到了与其串口通信相关的一些奇怪行为,但我不确定是什么" layer"通信导致了这个问题,因此它甚至可能与我对QSerialPort的使用无关。目前,关于其起源的唯一提示是QSerialPort在我看到不良行为开始之前不久发出ResourceError。这ResourceError可以与我对waitForBytesWritten的使用有关吗?

我在Debian 7系统上使用Qt 5.5.1。

1 个答案:

答案 0 :(得分:1)

在Unix上,读取通知将EAGAINEIOEBADF映射到ResourceError,并停止处理进一步的读取通知,直到您调用waitFor功能或直到您重新打开端口。这使得QSerialPort在重新打开或waitFor之前无法读取。将EIOEBADF映射到同一错误也许是有意义的。但是,由于EAGAIN是良性的,看起来像Qt错误就是这样对待它。

要查看是否存在问题,您可以清除错误并重新启动waitForReadyRead(0)waitForBytesWritten(0)并查看错误是否再次出现:

bool retryWaitForBytesWritten(int n, int retries = 10) {
  if (!dev.bytesToWrite()) return false;
  while (retries-- && !dev.waitForBytesWritten(n)) {
    if (dev.error() != QSerialPort::ResourceError)
      return false;
    dev.clearError(); // retry if it was a resource error
  }
  return true;
}

您主要不应该使用waitForBytesWritten,因为:

  1. 它没有做你认为它做的事情。它确保的所有内容都是通过使用QSerialPort系统调用将数据提供给操作系统来清空write中的内部写缓冲区。这并不意味着数据已经过物理发送。

  2. 它可能阻止。它不能保证阻止。

  3. 通常不需要了解写入完成的时间。如果你有一个半双工(基本上)命令响应协议,你完成发送时并不关心,但是当响应已经到达或超时时。

    如果你有一个流协议,你发送轮询命令并且不想生成它们比设备可以消耗它们更快,你应该发出命令(并在数据结构中将它们标记为“已发布”),直到bytesToWrite超过了水印。然后暂停添加新命令,直到bytesWritten信号通知发送缓冲区处于低水印。 readyRead位置匹配对已发布命令的响应并正确指示它们。

  4. 因此,理想情况下,您的代码应包含零waitXxx次调用。我们周围的世界是异步的。你不等待事情,你对他们做出反应。在事情发生之前不要停止事件循环 - 这是倒退。在你等待的那件事之前可能会发生其他事情,而你却忽略了它们。即使你将I / O推送到一个单独的线程,你仍然在浪费整个线程只是阻塞。处理重定向控制流的事件在顺序伪同步代码中也很难 - 所有内容都会被(必要的!)错误检查所困扰。有时人们会使用异常,这可能没问题,但是在大型代码库中很难正确执行,并且只要您希望将控制重定向到不在调用堆栈中的代码。当您将代码明确表达为分层状态机时,在面对异步事件(例如传入数据或错误)时,更容易推断代码的正确性。

    设计系统,使其根据异步发生的事件进展。如果您的UI的某些方面必须指明正在发送的数据的状态,那么只需这样做:不要等待,不要阻止该线程。

    This answer演示了如何使用状态机框架为基于QIODevice的设备实现异步I / O.一旦你建立了状态,将它们与UI行为联系起来就相对容易了。请参阅以下答案:onetwothreefourfivesixseven,{{3} }。

    另外,我错了,我修正了另一个答案。 QSerialPort不会重新进入事件循环,网络模块中的套接字也不会重新进入。