0

Here: Qt multi-thread with GUI I've learned how to create a background worker (the Engine class).

In that class, I have a QSerialPort object, that runs in the main thread (see How to setup QSerialPort on a separate thread?).

I'm using the signal/slot mechanism to send/receive data. But it works in one-way only. The signals emitted from the Engine object ("worker thread") are received by the QSerialPort ("main thread"). The viceversa doesn't work: any signal emitted from QSerialPort is not received by the Engine.

engine.h

#ifndef ENGINE_H
#define ENGINE_H

#include <QObject>
#include <QTimer>

#include "myserial.h"

class Engine : public QObject
{
    Q_OBJECT

public:
    explicit Engine(QObject *parent = 0);

private:
    QTimer m_timer;
    MySerial m_serial;

signals:
    void serialSendMessage(QByteArray data);

private slots:
    void lineReceived(QByteArray line);
    void foo();

public slots:
    void run();
    void open(QString port, quint32 baudrate);
    void close();

};

#endif // ENGINE_H

engine.c

#include "engine.h"
#include <QDebug>

Engine::Engine(QObject *parent) : QObject(parent)
{
    connect(&m_timer, &QTimer::timeout, this, &Engine::foo);
    m_timer.setInterval(200);
}

// THIS IS NEVER EXECUTED!
void Engine::lineReceived(QByteArray line)
{
    qDebug() << line;
}

// THIS IS RECEIVED BY QSERIALPORT
void Engine::foo()
{
    emit serialSendMessage("Hello World!");
}

void Engine::run()
{
    // THIS DOESN'T WORK!
    connect(&m_serial, &MySerial::lineReceived, this, &Engine::lineReceived);

    // THIS WORK!
    connect(this, &Engine::serialSendMessage, &m_serial, &MySerial::sendMessage);
}

void Engine::open(QString port, quint32 baudrate)
{
    m_serial.open(port, baudrate);

    QTimer::singleShot(0, &m_timer, static_cast<void (QTimer::*)(void)>(&QTimer::start));
}

void Engine::close()
{
    m_serial.close();
}

MySerial.h

#ifndef MYSERIAL_H
#define MYSERIAL_H

#include <QObject>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>

class MySerial : public QSerialPort {
    Q_OBJECT

public:
    explicit MySerial(QObject *parent = 0);
    bool open(QString port, quint32 baudrate);
    using QSerialPort::open;
    QByteArray sendMessage(QByteArray data, bool nmea);

signals:
    void lineReceived(QByteArray line);

private slots:
    void onReadyRead();
};

#endif // MYSERIAL_H

MySerial.c

#include "myserial.h"
#include <QDebug>

MySerial::MySerial(QObject *parent) : QSerialPort(parent) {
}

bool MySerial::open(QString port, quint32 baudrate)
{
    disconnect(this, 0, 0, 0);
    connect(this, &FemtoSerial::readyRead, this, &MySerial::onReadyRead);

    setPortName(port);
    if (!open(QIODevice::ReadWrite)) return false;
    setDataBits(QSerialPort::Data8);
    setParity(QSerialPort::NoParity);
    setStopBits(QSerialPort::OneStop);
    setBaudRate(baudrate);
    setFlowControl(QSerialPort::NoFlowControl);
    return true;
}

void MySerial::onReadyRead() {
    static QList<QByteArray> lines;
    static QByteArray buffer;

    buffer += readAll();
    int index = buffer.indexOf("\r");
    while (index != -1) {
        lines.append(buffer.left(index + 1));
        buffer = buffer.mid(index + 1);
        index = buffer.indexOf("\r");
    }

    // THIS SIGNAL IS EMITTED!
    while (!lines.isEmpty()) emit lineReceived(lines.takeFirst());
}

QByteArray MySerial::sendMessage(QByteArray data) {
    write(data);
    return data;
}

EDIT

Trying to add a QEventLoop:

void Engine::run()
{
    QEventLoop loop;
    connect(&m_serial, &MySerial::lineReceived, this, &Engine::lineReceived);
    connect(this, &Engine::serialSendMessage, &m_serial, &MySerial::sendMessage);    
    loop.exec();
}

The behavior is the same: the data is sent, but the receiving slot is never executed.

Mark
  • 4,338
  • 7
  • 58
  • 120
  • 1
    `void Engine::run()` connects a couple of signals and finishes with its thread immediately. It supposed to have the event loop as we told you for your last question. – Alexander V Aug 07 '17 at 18:55
  • I apologize, I didn't fully understand your comments. I'm re-reading them and the related links. – Mark Aug 07 '17 at 19:15
  • I give up, I cannot understand where and why I need `QEventLoop`. I move the whole `Engine` in another thread, I don't sub-class `QThread` (I tried to catch the `finished` signal and it doesn't fire). Furthermore the emitted signals work. Anyway, adding a `QEventLoop` in the `run()` function doesn't change the behavior. Would you mind to clarify me what's happening? – Mark Aug 07 '17 at 19:34
  • When void `Engine::run()` finishes its work the `Engine` thread is done. Call its base class. `QThread::run()` before exiting to have an event loop. – drescherjm Aug 07 '17 at 20:49
  • Uhm... but the `finished` signal is not emitted and it continues to send data! As said, adding a `QEventLoop` inside the run() function changes nothing. Would you please show me how should change the code? – Mark Aug 07 '17 at 20:51
  • Your sending is probably not happening in the thread. It's most likely running in the main thread. – drescherjm Aug 07 '17 at 20:54
  • MySerial exists in whatever thread that created the Engine. Not the engine thread. – drescherjm Aug 07 '17 at 20:58
  • @drescherjm I printed out the thread's address. The "sending" happens in the `Engine` thread (when emitting the signal) and ends in the main thread (when the slot is executed inside `QSerialPort`). And this unfortunately this does not help me. – Mark Aug 07 '17 at 20:58
  • @drescherjm correct! MySerial lives in the main thread, as said in the original question and as suggested in the link provided.... – Mark Aug 07 '17 at 20:59
  • It's hard to debug this in my head.. You may need `Qt::QueuedConnection` on your connect()s however Qt should have complained to std::error about cross thread signal /slots. – drescherjm Aug 07 '17 at 21:00
  • 1
    @Mark, try not using additional thread. Use all signals and slots that help available and never block the thread until the condition is up. That will make things clearer. – Alexander V Aug 07 '17 at 21:02
  • @AlexanderVX of course if I don't use any thread all works fine! I've used this code for years. But now I want to use the threads. And I'm trying to understand how... – Mark Aug 07 '17 at 21:03
  • Then do not make child from QThread. It is never needed. Create some Worker derived from QObject and move it onto new thread. Connect signals/from to there. – Alexander V Aug 07 '17 at 21:04
  • The comment directly above this one is the official Qt recommended way. With that said I have successfully done threading in Qt both ways many times. – drescherjm Aug 07 '17 at 21:06
  • @AlexanderVX I'm sorry, I received a lot of suggestion but no working code... I tried to follow your hints but I can't understand what. I provided a minimal example as requested. – Mark Aug 07 '17 at 21:06
  • Is Qt complaining to stderror about your signal slot connections? – drescherjm Aug 07 '17 at 21:09
  • No, nothing appears on the output. – Mark Aug 07 '17 at 21:09
  • Did you try changing `connect(&m_serial, &MySerial::lineReceived, this, &Engine::lineReceived);` to `connect(&m_serial, &MySerial::lineReceived, this, &Engine::lineReceived,Qt::QueuedConnection);` – drescherjm Aug 07 '17 at 21:15
  • Yes, I've already tried - anyway the auto connection should automatically select the QueuedConnection due to the cross-thread connection. – Mark Aug 07 '17 at 21:16
  • Also any chance lineReceived() is emitted before the thread starts? – drescherjm Aug 07 '17 at 21:16
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/151314/discussion-between-mark-and-drescherjm). – Mark Aug 07 '17 at 21:17

1 Answers1

1
bool MySerial::open(QString port, quint32 baudrate)
{
    //<s>disconnect(this, 0, 0, 0);</s> // <--- strikeout
    connect(this, &FemtoSerial::readyRead, this, &MySerial::onReadyRead);
    // ...
}

From the documentation:

Disconnect everything connected to an object's signals

disconnect(myObject, 0, 0, 0);

it means from the signals of myObject not the other way round. That line prevents the slot in Engine to be executed because it has just disconnected from the source signal.

Engine might disconnect when the port closes, to avoid multiple connections on the next opening.

Community
  • 1
  • 1
Mark
  • 4,338
  • 7
  • 58
  • 120