Как реализовать QThread, который работает forever{} с QWaitCondition, но все равно должен поймать другой слот при этом
я реализовал класс, который может записывать данные в последовательный порт через QQueue и считывать из него слот. Я использую QAsyncSerial для этого, который в свою очередь использует boost::asio с обратным вызовом. Класс перемещается в поток, и его метод start () выполняется, когда QThread выдает "started ()"
проблема в том, что я dequeue QQueue в методе start () -, используя forever {} и QWaitCondition. Пока это работает (который, очевидно, работает вечно) слот, подключенный к dataReceived сигнал QAsyncSerial не может быть вызван, поэтому я никогда ничего не читал из последовательного порта.
каков обычный подход к этой проблеме?
SerialPortHandler::SerialPortHandler(SerialPort serialPort, QObject *parent) : QObject(parent), serialPort(serialPort)
{
m_enqueueMessageMutex = new QMutex();
m_messageQueue = new QQueue<BaseMessage*>();
m_waitCondition = new QWaitCondition();
serial.open(serialPort.deviceName(), 2400);
connect(&serial, SIGNAL(dataReceived(QByteArray)), this, SLOT(serialSlotReceivedData(QByteArray)));
}
void SerialPortHandler::serialSlotReceivedData(QByteArray line)
{
qDebug() << QString(line).toAscii();
}
void SerialPortHandler::sendTestPing()
{
PingMessage *msg = new PingMessage();
enqueueMessage(msg);
}
void SerialPortHandler::enqueueMessage(BaseMessage *msg)
{
QMutexLocker locker(m_enqueueMessageMutex);
m_messageQueue->enqueue(msg);
m_waitCondition->wakeAll();
}
void SerialPortHandler::start()
{
if (!serial.isOpen())
return;
forever {
m_enqueueMessageMutex->lock();
if (m_messageQueue->isEmpty())
m_waitCondition->wait(m_enqueueMessageMutex);
BaseMessage *msg = m_messageQueue->dequeue();
serial.write(msg->encodeForWriting());
m_enqueueMessageMutex->unlock();
}
}
измененный обратный вызов QAsyncSerial, используемый boost:: asio:
void QAsyncSerial::readCallback(const char *data, size_t size)
{
emit dataReceived(QByteArray::fromRawData(data, (int) size));
}
Edit:
я решил эту проблему с помощью другого подхода. Я бросил QAsyncSerial и вместо этого использовал CallbackAsyncSerial, который также распространяется qasyncserial напрямую. Теперь обратный вызов используется по boost:: asio является "слотом"serialSlotReceivedData. Это "решает" проблему, так как обратный вызов вызывается в потоке boost::asio. Поскольку у него есть свой собственный поток, не имеет значения, что поток SerialPortHandler работает в заблокирован циклом forever.
новый код: (поскольку QAsyncSerial-это что-то вроде обертки для CallbackAsyncSerial, изменились только некоторые тривиальные вещи)
SerialPortHandler::SerialPortHandler(SerialPort serialPort, QObject *parent) : QObject(parent), serialPort(serialPort)
{
m_enqueueMessageMutex = new QMutex();
m_messageQueue = new QQueue<BaseMessage*>();
m_waitCondition = new QWaitCondition();
/* serial is now CallbackAsyncSerial and not QAsyncSerial */
serial.open(QString(serialPort.deviceName()).toStdString(), 2400);
serial.setCallback(bind(&SerialPortHandler::serialSlotReceivedData, this, _1, _2));
m_messageProcessingState = MessageProcessingState::Inactive;
}
void SerialPortHandler::start()
{
if (!serial.isOpen())
return;
forever {
m_enqueueMessageMutex->lock();
if (m_messageQueue->isEmpty())
m_waitCondition->wait(m_enqueueMessageMutex);
BaseMessage *msg = m_messageQueue->dequeue();
QByteArray encodedMessage = msg->encodeForWriting();
serial.write(encodedMessage.constData(), encodedMessage.length());
m_enqueueMessageMutex->unlock();
}
}
3 ответов
1) Создайте слот в потоке, например onMessageReady (), который будет выполнять эту работу.
2) создать сигнал означает, что новое сообщение готово, и излучать его каждый раз, когда вы создаете новое сообщение.
3) подключите их с помощью QueuedConnection и вызовите функцию exec вашего потока.
это не будет блокировать ваш поток, как WaitforObject, и вы будете обрабатывать все входящие сигналы.
что-то вроде этого:
SerialPortHandler: public QThread
{
Q_OBJECT
...
signals:
void sNewMessageReady();
slots:
void onNewMessageReady();
void serialSlotReceivedData(QByteArray);
};
SerialPortHandler::SerialPortHandler(SerialPort serialPort, QObject *parent) : QThread(parent), serialPort(serialPort)
{
m_enqueueMessageMutex = new QMutex();
m_messageQueue = new QQueue<BaseMessage*>();
serial.open(serialPort.deviceName(), 2400);
connect(&serial, SIGNAL(dataReceived(QByteArray)), this, SLOT(serialSlotReceivedData(QByteArray)));
connect(this, SIGNAL(sNewMessageReady()), this, SLOT(onNewMessageReady()),Qt::QueuedConnection);
}
void SerialPortHandler::enqueueMessage(BaseMessage *msg)
{
QMutexLocker locker(m_enqueueMessageMutex);
m_messageQueue->enqueue(msg);
emit sNewMessageReady();
}
void SerialPortHandler::onNewMessageReady()
{
QMutexLocker locker(m_enqueueMessageMutex);
BaseMessage *msg = m_messageQueue->dequeue();
serial.write(msg->encodeForWriting());
}
в конце концов просто вызовите метод exec () потока, вам не нужно переопределять run() и использовать QWaitCondotion вообще.
это своего рода выстрел в темноте, так как я довольно новичок в использовании Qt, и я не знаю "обычных" подходов к таким проблемам, но, возможно, вызов QCoreApplication::processEvents
внутри цикла поможет.
Если это не является строго необходимым по какой-то причине, я бы избавился от QWaitCondition. Вместо этого, когда have enqueueMessage() испускает сигнал (Qt) после того, как он добавил новые данные в QQueue, и ваш рабочий поток получает этот сигнал (вместе с любыми другими сигналами, которые он должен получить) обычным способом Qt. Тогда ваша проблема уходит, без тайм-аутов или других хакерских потребностей.
(опционная оптимизация: имейте серийный порт только испустить сигнал если QQueue было пустой, прежде чем он добавил новые данные, и соответствующий слот основного потока считывается из QQueue, пока QQueue не будет пустым - это может сократить количество сигналов, которые необходимо отправить)