Как реализовать 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 не будет пустым - это может сократить количество сигналов, которые необходимо отправить)