简体   繁体   English

如何使用 QWaitCondition 实现一个永远运行的 QThread{},但在执行此操作时仍需要捕获另一个 Slot

[英]How to implement a QThread that runs forever{} with a QWaitCondition but still needs to catch another Slot while doing that

I implemented a class that can write data to a serial port via a QQueue and read from it by a slot.我实现了一个 class,它可以通过 QQueue 将数据写入串行端口并通过插槽从中读取。 I use QAsyncSerial for this which in turn uses boost::asio with a callback.我为此使用 QAsyncSerial ,它反过来使用 boost::asio 和回调。 The class is moved to a thread and its start() method is executed when the QThread emits "started()" class 被移动到一个线程并在 QThread 发出“started()”时执行它的 start() 方法

The problem is that I dequeue the QQueue in the start()-method using forever {} and a QWaitCondition.问题是我在 start() 方法中使用 forever {} 和 QWaitCondition 使 QQueue 出队。 While this is running (which obviously runs forever) the slot connected to the dataReceived signal of QAsyncSerial can not be called, thus I never read anything from the serial port.虽然它正在运行(显然永远运行),但无法调用连接到 QAsyncSerial 的 dataReceived 信号的插槽,因此我从未从串行端口读取任何内容。

What is the usual approach to this problem?解决这个问题的常用方法是什么?

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();
    }
}

The changed QAsyncSerial callback used by boost::asio: boost::asio 使用的更改后的 QAsyncSerial 回调:

void QAsyncSerial::readCallback(const char *data, size_t size)
{
    emit dataReceived(QByteArray::fromRawData(data, (int) size));
}

Edit:编辑:

I solved this problem with another approach.我用另一种方法解决了这个问题。 I ditched QAsyncSerial and instead used the CallbackAsyncSerial which is also distributed by QAsyncSerial directly.我放弃了 QAsyncSerial,而是使用了 CallbackAsyncSerial,它也由 QAsyncSerial 直接分发。 Now the callback used by boost::asio is the serialSlotReceivedData "slot".现在 boost::asio 使用的回调是 serialSlotReceivedData "slot"。 This "solves" the problem since the callback is called in the thread boost::asio runs in. Since it has its own thread it doesn't matter that the thread SerialPortHandler runs in is blocked by the forever loop.这“解决”了问题,因为回调在线程 boost::asio 运行中被调用。由于它有自己的线程,因此 SerialPortHandler 运行的线程被永远循环阻塞并不重要。

New code: (since QAsyncSerial is something like a wrapper to CallbackAsyncSerial only some trivial things changed)新代码:(因为 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();
    }
}

1) Create slot in your thread, for example onMessageReady(), which will do the job. 1) 在您的线程中创建插槽,例如 onMessageReady(),它将完成这项工作。

2) Create a signal indicates that new message ready, and emit it each time you creating a new message. 2)创建信号表示新消息准备就绪,并在每次创建新消息时发出。

3) Connect them using QueuedConnection and call your thread's exec function. 3) 使用 QueuedConnection 连接它们并调用线程的 exec function。

This won't block your thread, as WaitforObject does, and you will handle all incoming signals.这不会像 WaitforObject 那样阻塞您的线程,并且您将处理所有传入的信号。

something like this:像这样的东西:

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());
}

after all simply call thread's exec() method, you don't need to reimplement run() and to use QWaitCondotion at all.毕竟只是调用线程的 exec() 方法,您根本不需要重新实现 run() 并使用 QWaitCondotion。

This is kind of a shot in the dark, since I'm pretty new to using Qt and I don't know the "usual" approaches to problems like this, but perhaps a call to QCoreApplication::processEvents within the loop would help.这有点像在黑暗中拍摄,因为我对使用 Qt 还很陌生,而且我不知道解决此类问题的“通常”方法,但也许在循环中调用QCoreApplication::processEvents会有所帮助。

Unless it's strictly necessary for some reason, I'd get rid of the QWaitCondition.除非出于某种原因绝对必要,否则我会摆脱 QWaitCondition。 Instead, when the have enqueueMessage() emit a (Qt) signal after it has appended new data to the QQueue, and have your worker thread receive that signal (along with whatever other signals it needs to receive) in the usual Qt way.相反,当有 enqueueMessage() 在将新数据附加到 QQueue 后发出 (Qt) 信号时,并让您的工作线程以通常的 Qt 方式接收该信号(以及它需要接收的任何其他信号)。 Then your problem goes away, with no timeouts or other hackery needed.然后你的问题就消失了,不需要超时或其他黑客行为。

(optional optimization: have the serial port only emit the signal if the QQueue was empty before it added the new data, and have the main thread's corresponding slot read from the QQueue until the QQueue is empty -- that can cut down on the number of signals that need to be sent) (可选优化:只有在QQueue为空的情况下,串口才会在添加新数据之前发出信号,并从QQueue中读取主线程对应的slot,直到QQueue为空——这样可以减少需要发送的信号)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM