在an answer on another question中,它声称当使用带有QSerialPort
的主要Qt事件循环时,QSerialPort::waitForBytesWritten
会导致"微妙的错误"因为它会打开另一个事件循环。
我使用waitForBytesWritten
方法确保在写入所有字节之前不会重新输入主事件循环。这不是一种有效的方法吗?什么"微妙的错误"会发生什么?我应该使用flush()
代替,并确保我的事件循环不再再次输入,直到所有未决字节都被写入?
我我在我的应用程序中看到了与其串口通信相关的一些奇怪行为,但我不确定是什么" layer"通信导致了这个问题,因此它甚至可能与我对QSerialPort
的使用无关。目前,关于其起源的唯一提示是QSerialPort
在我看到不良行为开始之前不久发出ResourceError
。这ResourceError
可以与我对waitForBytesWritten
的使用有关吗?
我在Debian 7系统上使用Qt 5.5.1。
答案 0 :(得分:1)
在Unix上,读取通知将EAGAIN
,EIO
和EBADF
映射到ResourceError
,并停止处理进一步的读取通知,直到您调用waitFor
功能或直到您重新打开端口。这使得QSerialPort
在重新打开或waitFor
之前无法读取。将EIO
和EBADF
映射到同一错误也许是有意义的。但是,由于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
,因为:
它没有做你认为它做的事情。它确保的所有内容都是通过使用QSerialPort
系统调用将数据提供给操作系统来清空write
中的内部写缓冲区。这并不意味着数据已经过物理发送。
它可能阻止。它不能保证阻止。
通常不需要了解写入完成的时间。如果你有一个半双工(基本上)命令响应协议,你完成发送时并不关心,但是当响应已经到达或超时时。
如果你有一个流协议,你发送轮询命令并且不想生成它们比设备可以消耗它们更快,你应该发出命令(并在数据结构中将它们标记为“已发布”),直到bytesToWrite
超过了水印。然后暂停添加新命令,直到bytesWritten
信号通知发送缓冲区处于低水印。 readyRead
位置匹配对已发布命令的响应并正确指示它们。
因此,理想情况下,您的代码应包含零waitXxx
次调用。我们周围的世界是异步的。你不等待事情,你对他们做出反应。在事情发生之前不要停止事件循环 - 这是倒退。在你等待的那件事之前可能会发生其他事情,而你却忽略了它们。即使你将I / O推送到一个单独的线程,你仍然在浪费整个线程只是阻塞。处理重定向控制流的事件在顺序伪同步代码中也很难 - 所有内容都会被(必要的!)错误检查所困扰。有时人们会使用异常,这可能没问题,但是在大型代码库中很难正确执行,并且只要您希望将控制重定向到不在调用堆栈中的代码。当您将代码明确表达为分层状态机时,在面对异步事件(例如传入数据或错误)时,更容易推断代码的正确性。
设计系统,使其根据异步发生的事件进展。如果您的UI的某些方面必须指明正在发送的数据的状态,那么只需这样做:不要等待,不要阻止该线程。
This answer演示了如何使用状态机框架为基于QIODevice
的设备实现异步I / O.一旦你建立了状态,将它们与UI行为联系起来就相对容易了。请参阅以下答案:one,two,three,four,five,six,seven,{{3} }。
另外,我错了,我修正了另一个答案。 QSerialPort
不会重新进入事件循环,网络模块中的套接字也不会重新进入。