阻塞主机示例(Blocking Master)
Blocking Master展示了如何在工作线程中使用QSerialPort的同步(synchronous)API为串行接口创建应用程序。
QSerialPort
支持两种编程方案:异步(asynchronous (non-blocking)非阻塞)
方案。当控件返回到Qt事件循环时,将调度并执行操作。操作完成后,QSerialPort类将发出信号。例如,write()
方法立即返回。当数据发送到串行端口时,QSerialPort类将发出bytesWritten()
信号。同步(synchronous (blocking)阻塞)
方案。在无头(headless)和多线程应用程序中,可以调用wait方法(在这种情况下为waitForReadyRead())来挂起调用线程,直到操作完成为止。在此示例中,演示了同步方案。
终端(Terminal)
示例说明了异步方案。本示例的目的是演示如何在不丢失用户界面响应能力的情况下简化串行编程代码。阻塞的串行编程API通常会导致代码更简单,但应仅在非GUI线程中使用它,以保持用户界面的响应速度。
该应用程序是主要的,它演示了与从属应用程序Blocking Slave Example配对的工作。
主应用程序通过串行端口向从属应用程序发起传输请求,并等待响应。
class MasterThread : public QThread
{
Q_OBJECT
public:
explicit MasterThread(QObject *parent = nullptr);
~MasterThread();
void transaction(const QString &portName, int waitTimeout, const QString &request);
void run() Q_DECL_OVERRIDE;
signals:
void response(const QString &s);
void error(const QString &s);
void timeout(const QString &s);
private:
QString portName;
QString request;
int waitTimeout;
QMutex mutex;
QWaitCondition cond;
bool quit;
};
MasterThread是一个QThread子类,提供用于调度对从属服务器的请求的API。 此类提供了用于响应和报告错误的信号。 可以调用transaction()
方法以使用所需的请求启动新的主事务。 结果由response()
信号提供。 如果出现任何问题,将发出error()
或timeout()
信号。
注意,transaction()
方法是在主线程中调用的,而请求是在MasterThread线程中提供的。 MasterThread数据成员在不同的线程中并发读取和写入,因此QMutex
类用于同步访问。
void MasterThread::transaction(const QString &portName, int waitTimeout, const QString &request)
{
QMutexLocker locker(&mutex);
this->portName = portName;
this->waitTimeout = waitTimeout;
this->request = request;
if (!isRunning())
start();
else
cond.wakeOne();
}
transaction()
方法存储串行端口名称,超时和请求数据。 可以使用QMutexLocker
锁定互斥锁以保护此数据。 线程可以启动,除非它已经在运行。 稍后将讨论wakeOne()
方法。
void MasterThread::run()
{
bool currentPortNameChanged = false;
mutex.lock();
QString currentPortName;
if (currentPortName != portName) {
currentPortName = portName;
currentPortNameChanged = true;
}
int currentWaitTimeout = waitTimeout;
QString currentRequest = request;
mutex.unlock();
在run()
函数中,首先是锁定QMutex
对象,然后使用成员数据获取串行端口名称,超时和请求数据。 完成此操作后,将释放QMutex
锁。
在任何情况下都不应在获取数据的过程中同时调用transaction()
方法。 注意,虽然QString类是可重入的,但它不是线程安全的。 因此,建议不要在请求线程中读取串行端口名称,而在另一个线程中超时或请求数据。 MasterThread类一次只能处理一个请求。
在进入循环之前,将在run()
方法中的堆栈上构造QSerialPort
对象:
QSerialPort serial;
if (currentPortName.isEmpty()) {
emit error(tr("No port name specified"));
return;
}
while (!quit) {
这样就可以在运行循环时创建对象。 这也意味着所有对象方法都在run()
方法的范围内执行。
在循环内部检查当前事务的串行端口名称是否已更改。 如果已更改,则重新打开串行端口,然后重新配置。
if (currentPortNameChanged) {
serial.close();
serial.setPortName(currentPortName);
if (!serial.open(QIODevice::ReadWrite)) {
emit error(tr("Can't open %1, error code %2")
.arg(portName).arg(serial.error()));
return;
}
}
循环将继续请求数据,写入串行端口并等待,直到所有数据都被传输为止。
// write request
QByteArray requestData = currentRequest.toLocal8Bit();
serial.write(requestData);
if (serial.waitForBytesWritten(waitTimeout)) {
警告:至于阻塞传输,应在每次write方法调用之后使用waitForBytesWritten()
方法。 这将处理所有I / O例程,而不是Qt事件循环。
如果传输数据时发生超时错误,则发出timeout()
信号。
} else {
emit timeout(tr("Wait write request timeout %1")
.arg(QTime::currentTime().toString()));
}
成功请求后,有一个等待期的响应,然后再次读取。
// read response
if (serial.waitForReadyRead(currentWaitTimeout)) {
QByteArray responseData = serial.readAll();
while (serial.waitForReadyRead(10))
responseData += serial.readAll();
QString response(responseData);
emit this->response(response);
警告:至于阻塞替代方法,应在每次read()
调用之前使用waitForReadyRead()
方法。 这将处理所有I / O例程,而不是Qt事件循环。
如果接收数据时发生超时错误,则发出timeout()信号。
} else {
emit timeout(tr("Wait read response timeout %1")
.arg(QTime::currentTime().toString()));
}
成功完成事务后,response()信号包含从从应用程序接收的数据:
emit this->response(response);
之后,线程进入睡眠状态,直到出现下一个事务。 线程在使用成员唤醒后读取新数据,并从头开始运行循环。
mutex.lock();
cond.wait(&mutex);
if (currentPortName != portName) {
currentPortName = portName;
currentPortNameChanged = true;
} else {
currentPortNameChanged = false;
}
currentWaitTimeout = waitTimeout;
currentRequest = request;
mutex.unlock();
}
网友评论