QSerialPort - blocking vs. non blocking confusion



  • I'm working with Qt 5.2 and I'm trying to understand how to best use QSerialPort from the documentation and examples. But I ended up with a bit of a mess in my code and I'd appreciate some clarification.

    I want to use an asynchronous approach for reading, and use the readyRead signal. However, my code is already running inside a worker thread without a Qt event loop. So I'm not sure if I can even use the signals and slots.

    I saw an example where the code used waitForBytesWritten() after the write() call but at this point I'm not sure I understand anymore what approach I'm looking at. I think I'm at some hybrid state which is just plain incorrect.

    Could anyone please explain the two approaches and which methods are used for reading+writing in each one? And also, if I'm in a thread without the event loop, can I use the signals or must I use the waitForxxx methods?



  • It is better - to use an async approach, even in another thread.

    In the theory asynchronous approach implies that the I/O operations are executed asynchronously (without blocking). So, the read() and write() methods alvays returns immediately.

    It is also important to note that QtSerialPort always works in the buffered mode (contains two internal buffers). I.e. the write() will simply copy data to the internal writeBuffer, and read() will simply take data from the internal readBuffer.

    But for data transfering from the writeBuffer to the device and data acquisition from the device to readBuffer is requires an Qt event-loop. Thing in that all I/O operations are executed by system (platform-specific) events from the descriptors of device or descriptors of the I/O operations (depends from host OS).

    Thus the internal subsystem of QtSerialPort automatically is notified, e.g. about that in the Rx FIFO of the device there are data for reading and they can be read, or that the Tx FIFO of the device is empty and data can be filled to write. And will be emitted are readyRead() or bytesWritten() signals.

    So, functioning of the mechanism of events requires an Qt event-loop.
    E.g. this code
    @
    Foo::read() {
    while (1) {
    QByteArray data = port.read();
    }
    }
    @
    will not work because no Qt event-loop entry. And will be returns always empty data.

    But this code is correct:
    @
    ...
    // notify about data available for read
    connect(port, SIGNAL(readyRead()), this, SLOT(handleReadyRead());
    ...
    Foo::handleReadyRead() {
    QByteArray data = port.read();
    }
    @
    or:
    @
    ...
    // polling by timer
    connect(timer, SIGNAL(timeout()), this, SLOT(handleTimeout());
    ...
    Foo::handleTimeout() {
    QByteArray data = port.read();
    }
    @

    But is possible to use the QtSerialPort in blocking (synch) mode when Qt event-loop isn't used for the QtSerialPort. But in this case need do use an separate thread because blocking I/O operations will freeze a main thread.

    A main feature of implementation of the QtSerialPort that for the Qt event-loop's "emulation" in the synchronous mode it is necessary to use the waitForReadyRead() and waitForBytesWritten() methods. This methods are blocking!

    These waitForXX() methods also trace events on descriptors and to do same as event-loop. It should be noted that the read() and write() methods also do nothing, they just do read/write from the internal buffers.

    So, this code is valid:
    @
    Foo::read() {
    while (1) {
    if (waitForReadyRead(500) {
    qint64 av = port.bytesAvailable();
    if (av < expected)
    continue;
    QByteArray data = port.read();
    } else {
    // timeout error (no more data for read or critical failure.
    }
    }
    }
    @

    In this mode use of signals of slots is also possible, e.g.:
    @

    class Bar : public QObject
    {
    public:
    Bar(QObject *parent);
    public slots:
    void handleParsedData(const QByteArray &data);
    }

    class Foo : public QThread
    {
    public:
    Foo(QObject *parent);
    protected:
    void run();
    signals:
    void dataParsed(const QByteArray &data);
    }

    connect(foo, SIGNAL(dataParsed(QByteArray)), bar, SLOT(handleParsedData(QByteArray))));

    Foo::run() {
    while (1) {
    if (waitForReadyRead(500) {
    qint64 av = port.bytesAvailable();
    if (av < expected)
    continue;
    QByteArray data = port.read();
    // parse data
    ...
    // data successfully parsed
    QByteArray pd = ...
    emit dataParsed(pd);
    } else {
    // timeout error (no more data for read or critical failure.
    }
    }
    }
    @
    Of course, the Foo and Bar should be in different threads.



  • UPD: But in any case I recommend to use async approach, because the waitForXX() methods has a bugs, which is fixed in future Qt 5.2.1 release (or you can manually build sourses from Git). :)



  • First of all, thank you for your detailed answer!

    From what I see online, 5.2.1 should be released within a few weeks so I'm not too bothered by updating then.

    There's still something I didn't get (at least one thing, that is). My starting point was a worker thread without a Qt event loop (started without exec()). Within the context of that thread, I tried to connect to a readyRead signal and when I sent data to my port the slot wasn't called. And reading your explanation I thought that's because of not having the Qt event loop - meaning I should have used the waitFor methods to "Emulate" it as you say. But then I don't understand the part of the code you have as "but this code is correct". Without an event loop, that code still wouldn't work, no?



  • bq. I tried to connect to a readyRead signal and when I sent data to my port the slot wasn’t called.

    Please provide your code, what you want to do?

    bq. But then I don’t understand the part of the code you have as “but this code is correct”.

    I mean usual asynch way (with event-loop) with use readyRead() to notify about new data:
    @
    class Foo : public QObject
    {
    Q_OBJECT
    public:
    explicit Foo(QObject *parent)
    : QObject(parent)
    {
    connect(&p, SIGNAL(readyRead()), this, SLOT(handleReadyRead()));
    ...
    }

    private slots:
    void handleReadyRead()
    {
    if (p.bytesAvailable() < expexted)
    return;
    QByteArray date = p.readAll();
    ...
    }
    private:
    QSerialPort p;
    }
    @

    I mean usual asynch way (with event-loop) with use timeout() to polling a new data:
    @
    class Foo : public QObject
    {
    Q_OBJECT
    public:
    explicit Foo(QObject *parent)
    : QObject(parent)
    {
    connect(&t, SIGNAL(timeout()), this, SLOT(handleTimeout()));
    t.start(100);
    ...
    }

    private slots:
    void handleTimeout()
    {
    if (p.bytesAvailable() < expexted)
    return;
    QByteArray date = p.readAll();
    ...
    }
    private:
    QSerialPort p;
    QTimer t;
    }
    @

    Or I do not understand what to you do not understand? :)



  • I think I understand it now, thank you.



  • I am using Qt 5.1.1 on both a 64-bit Windows 7 and 32-bit XP systems. I was having trouble receiving with the synchronous method. I used the following to get WaitForReadyRead to work correctly:

    @QByteArray qbuffmsg;

    if(serial.waitForReadyRead(200)
    {
    qbuffmsg = serial.readAll();
    while(serial.waitForReadyRead(50))
    {
    qbuffmsg.append(serial.readAll());
    }
    }
    @

    The key is the "while(serial.waitForReadyRead(50))". The Windows 7 machine needed at least a 30ms timeout to capture the entire message consistently and the XP machine required 50ms timeout value. This works very consistently. I can tolerate these timeout values for now, but I hope they fix the underlying problem soon. :)



  • bq. I can tolerate these timeout values for now, but I hope they fix the underlying problem soon. :)

    Who are they? MS? :)

    It is normal because such small values (30 - 50 ms) are on border of switching of a context in Windows OS.

    You can try to use QSerialPort::handle() and to setup necessary read timeouts to the driver through "SetCommTimeouts":http://msdn.microsoft.com/en-us/library/windows/desktop/aa363437(v=vs.85).aspx.



  • Thanks for the suggestion. I posted the timeouts that worked because all the Qt examples I have seen use waitForReadyRead(10). A 10ms timeout fails much of the time. My app is used for a Modbus interface and the 50ms timeout is pretty long when I only need to see a 2ms period of no characters to detect a Modbus message. I will eventually try your suggestions for improving the performance. I am stuck with Windows for now. I want my QNX back. :)



  • Yes, I know, that the Modbus spec described about timeouts between chars (an 3.5 chars as I remember??). I simply would try to measure a timeout on the whole package instead of each character. But I agree, it also isn't the good decision.

    UPD: Also you can try to increase a thread priority..


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.