Как реализовать QThread, который работает вечно {} с QWaitCondition, но при этом все равно должен перехватывать другой слот - PullRequest
7 голосов
/ 23 мая 2011

Я реализовал класс, который может записывать данные в последовательный порт через QQueue и читать из него через слот. Я использую QAsyncSerial для этого, который в свою очередь использует boost :: asio с обратным вызовом. Класс перемещается в поток, и его метод start () выполняется, когда QThread испускает «start ()»

Проблема в том, что я удаляю 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 ]

3 голосов
/ 24 мая 2011

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.

2 голосов
/ 24 мая 2011

Если по какой-то причине это строго не нужно, я бы избавился от QWaitCondition.Вместо этого, когда have enqueueMessage () испускает сигнал (Qt) после того, как он добавил новые данные в QQueue, и ваш рабочий поток получает этот сигнал (наряду с любыми другими сигналами, которые ему нужно получить) обычным способом Qt.Тогда ваша проблема исчезнет, ​​без необходимости тайм-аутов или других хакерских атак.

(дополнительная оптимизация: последовательный порт должен излучать сигнал только в том случае, если QQueue был пуст, до того как были добавлены новые данные, и иметь соответствующий основной потокслот, считываемый из QQueue до тех пор, пока QQueue не станет пустым - это может сократить количество сигналов, которые необходимо отправить)

2 голосов
/ 23 мая 2011

Это своего рода выстрел в темноте, так как я довольно плохо знаком с использованием Qt и не знаю «обычных» подходов к таким проблемам, но, возможно, вызов QCoreApplication::processEvents внутри цикла поможет.

...