简体   繁体   中英

Signal and slot mechanism not working across non-UI threads

I have the current situation:

在此处输入图片说明

The Worker is a field in the MainWindow class, the Watchdog is a field in the Worker class.

The execution goes like this:

  • Worker is constructed

     class Worker : public QThread { Q_OBJECT public: explicit Worker(); void run(); private: Watchdog *watchdog; bool running = false; signals: void tick(); public slots: void ownerDied(); }; 
  • The constructor of Worker constructs a Watchdog on the heap

     class Watchdog : public QThread { Q_OBJECT public: Watchdog(); void run(); public slots: void tick(); signals: void ownerIsDead(); }; 
    • The constructor does QObject::connect() between the Watchdog and Worker signals and slots

       connect(this, SIGNAL(tick()), watchdog, SLOT(tick())); connect(watchdog, SIGNAL(ownerIsDead()), this, SLOT(ownerDied())); 
    • The main loop of the Worker starts in the Worker::run() method.

    • The Worker starts the Watchdog . The Watchdog loop is started.

    • If the Worker does not tick() within 5 seconds of the start() call, the Watchdog emits the ownerIsDead() signal

    • Worker processes the ownerDied() signal, killing the main Worker loop
    • If the Worker does tick the Watchdog , he sleeps another 5 seconds
    • The whole process repeats

The problem is, the tick() never reaches the Watchdog , nor does the ownerIsDead() signal reach the worker because it did not tick. Why?

Here is the raw code, the class names are a bit different.

watchdog.h

#ifndef WATCHDOG_H
#define WATCHDOG_H

#define THRESHOLD 1000

#include <QThread>
#include <QObject>

class Watchdog : public QThread
{

    Q_OBJECT

public:
    Watchdog();
    void run();

public slots:

    void tick();
    void kill();

private:

    bool running = false;
    bool ticked = false;

signals:

    void error();

};

#endif // WATCHDOG_H

watchdog.cpp

#include "watchdog.h"

#include <QDebug>

Watchdog::Watchdog()
{

}

void Watchdog::run()
{

    running = true;

    qDebug() << "Starting watchdog";

    while (running) {

        QThread::msleep(THRESHOLD);

        qDebug() << "Watchdog tick ... ";

        if (!ticked) {
            qDebug() << "read() or write() is read";
            emit error();
        }
    }

}

void Watchdog::tick()
{
    qDebug() << "Watchdog ticking";

    ticked = true;

}

void Watchdog::kill()
{

    qDebug() << "Killing watchdog...";

    running = false;

}

diskerror.h ( AKA the 'Worker' )

#ifndef DISKERROR_H
#define DISKERROR_H

#include <QThread>
#include <watchdog.h>

extern "C" {

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <malloc.h>

}

class DiskError : public QThread
{
    Q_OBJECT
public:
    explicit DiskError();
    void run();

private:
    int mismatch(char *a, char *b);
    Watchdog *watchdog;
    bool running = false;

signals:
    void tick();
    void killWatchdog();

public slots:
    void ownerIsDead();
};

#endif // DISKERROR_H

diskerror.cpp

include "diskerror.h"

#include "watchdog.h"

#include <QDebug>

#define BLKSZ 4096

DiskError::DiskError()
{

    watchdog = new Watchdog();

    connect(this, SIGNAL(killWatchdog()), watchdog, SLOT(kill()));
    connect(this, SIGNAL(tick()), watchdog, SLOT(tick()));
    connect(watchdog, SIGNAL(error()), this, SLOT(ownerIsDead()));

}

void DiskError::run()
{


    int fd = open("/dev/sdc", O_RDWR | O_SYNC);

    if (fd < 0) {
        qDebug() << strerror(errno);
    }

    size_t size;

    if (ioctl(fd, BLKGETSIZE64, &size) < 0) {
        qDebug() << "IOCTL Error";
        return;
    }


    size_t step = (size / 2500);
    size_t done = 0;

    int i = 0;

    char testing[BLKSZ];
    char pattern[BLKSZ];


    for (int i = 0; i < BLKSZ; i++) {
        pattern[i] = 0xCF;
    }

    int re, bb, wr;

    off_t curr = 0;

    watchdog->start();
    running = true;

    while (running) {

        lseek(fd, curr, SEEK_SET);

        wr = write(fd, pattern, BLKSZ); /* Write pattern to disk */

        lseek(fd, curr, SEEK_SET);

        re = read(fd, testing, BLKSZ); /* Read pattern back from disk */

        bb = mismatch(pattern, testing);

        curr += BLKSZ;
        done += BLKSZ;

        emit tick();

        if ( (re == 0) || (wr < 0) ) {

            qDebug() << "Flushing buffers...";

            sync();

            break;
        }

        if (done >= step) {

            if (bb) {
                qDebug() << "[" << i << "] Error occured";
            } else {
                qDebug() << "[" << i << "] OK";
            }


            done = 0;
            i++;

        }

    }

    emit killWatchdog();

    sync();

    if (close(fd) < 0) {
        qDebug() << "Error closing device";
    }

}

int DiskError::mismatch(char *a, char *b)
{

    for (int i = 0; i < BLKSZ; i++) {
        if (  (*(a+i)) != (*(b+i))  ) return 1;
    }

    return 0;

}

void DiskError::ownerIsDead()
{
    qDebug() << "read() call blocked for more than 5 seconds, device inoperable";
}

I never see the debug text in the worker class, nor do I see the tick text in the worker.

What could be happening is that the receiver object "belongs" to a different thread than the one doing the emit.

This kind of cross-thread signal/slot connections, so called Qt::QueuedConnection connections, require a running event loop in the thread of the receiver object.

If the receiver object has been created by one of the additional threads, then this thread needs to run its event loop, see QThread::exec()

Not sure you actually need the watchdog to be a separate thread, it seems in only checks a condition in regular intervals, something a QTimer on the main thread could easily do as well.

As stated in previous comments and answers by other guys, I think the central problem is that you have no event loop in your "threads", which is the responsible of managing (check, elaborate and do actions according) signals/slots. Also infinite loop is the worst thing to do, because it prevent your thread/application to "call" the event loop, so that signal/slot system becomes useless. You can find a more clear and extensive explenation here: https://wiki.qt.io/Threads_Events_QObjects

A trick to allow your while loop working with signal/slot is to call at each iteration the function QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags) . From Qt docs:

Processes all pending events for the calling thread according to the specified flags until there are no more events to process.

You can call this function occasionally when your program is busy performing a long operation (eg copying a file).

In event you are running a local loop which calls this function continuously, without an event loop, the DeferredDelete events will not be processed. This can affect the behaviour of widgets, eg QToolTip, that rely on DeferredDelete events to function properly. An alternative would be to call sendPostedEvents() from within that local loop.

Calling this function processes events only for the calling thread.

One last comment: the way you have implemented threading is not wrong, but is also not the way Qt suggests (but maybe the one you have choosen knowing what I'm saying). In fact, QThread objects are not the threads, but the objects managing threads. More information about this at this link of Qt docs: http://doc.qt.io/qt-4.8/qthread.html#details

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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