MayaPosch
MayaPosch

Reputation: 315

QThread never runs/finishes before it can be used?

I have created a custom QObject class called EncodeThread, which looks as follows:

class EncodeThread : public QObject {
    Q_OBJECT

public:
    void set(SWSL::Video* v, QStringList f, QDir vDir);
    void run();

public slots:
    void encode();

signals:
    void encodeProgress(int i);

private:
    SWSL::Video* video;
    QStringList files;
    QDir videoDir;
};

As may be obvious, this class is used for encoding a video using an external library. Encode() contains the actual encoding routine, run() is a function I added while troubleshooting, though it's obviously non-functional:

void EncodeThread::run() {
    if (currentThread() != this) {
        // caller is in different thread.
        QMetaObject::invokeMethod(this, "encode", Qt::QueuedConnection);
    }
    else {
        encode();
    }
}

The problem is when I use a QThread and the moveToThread() function on the EncodeThread instance, namely that nothing seems to happen. No data is written, and the instance never emits the signal which should save the encoded file to disk.

encThread.set(video, files, videoDir);
connect(&encThread, SIGNAL(encodeProgress(int)), cookVideoProgress, SLOT(setValue(int)));
    connect(&encThread, SIGNAL(finished()), this, SLOT(videoCookEnd()));
    connect(this, SIGNAL(videoEncode()), &encThread, SLOT(encode()));
encThread.moveToThread(&thread);
    thread.start();

The above is how the whole setup is started. EncThread and thread variables are declared in the MainWindow class. I have made the set() function of EncodeThread call encode() after attempts to call encode() from the main thread using signals and QMetaObject failed.

I'm not new to threading, having used native Windows and Linux threads, as well as those of various cross-platform implementations, but QThreads really seem to baffle me. Any suggestions are more than welcome :)

Upvotes: 2

Views: 1021

Answers (3)

Mohammad Rahimi
Mohammad Rahimi

Reputation: 1113

For future programmers I'm adding this answer. There are generally 3 approaches to multi-threading in Qt. Low Level, Reusing(lets say Mid Level) and High Level.

Low level also includes two different approaches. In Low level you can Inherit QThread class and provide the code that you want to run in parallel inside void QThread::run() override;. After calling QThread::start() on your drived class, code in run will execute.

Another approach in Low Level Multi-threading in Qt is relaying on Qt's event loops. In this way you create a class that is drived from QObject, not necessarily from QThread and then move that class to a new QThread with this. And after that you will call start() on that QThread object(this QThread object is an object of type QThread . You don't have to subclass QThread here. Just instantiate one object. It is where I believe you misunderstood in your code).

After calling start() on your QThread object, its event loop starts in another thread and signals from anywhere in your code that are connected to your QObject drived class's slots will run in that event loop in parallel as long as you don't use Qt::DirectConnection as last argument to this.

These two approaches has their own benefits and drawbacks. When sub-classing QThread and using Qthread::run(), if your code inside run is running inside a while(true) loop, one of your processor's threads will always be occupied with Qthread::run() code and not in the threadpool for other threads in your program.

The QObject::moveToThread() approach is useful for most cases. But if the QObject drived classe's slot is going to be called very frequently, like 100 or 1000 times per second, memory usage rises, due to possible arguments passed to the slot, and some of this signals may never reach the slot.

Upvotes: 0

Michael Burr
Michael Burr

Reputation: 340208

Probably way to late to be any help to you, but here's a little demo program that puts an EncoderThread class to work. It probably doesn't quite mesh with your design (which your question only had fragments of), but it demonstrates running an object instance on its own thread and wiring 2 objects on different threads via signals/slots to let them communicate:

#include <stdio.h>
#include <QObject>
#include <QThread>
#include <QtCore/QCoreApplication>

// QSleeper is just a toy utility class that makes the
//  protected QThread::sleep() family of functions
//  publicly accessible.  It's only use is for demo
//  programs like this
class Sleeper : QThread
{
public:
    static void sleep(unsigned long secs) { QThread::sleep(secs); }
    static void msleep(unsigned long msecs) { QThread::msleep(msecs); }
    static void usleep(unsigned long usecs) { QThread::usleep(usecs); }

};


// an Encoder class that maintains itself on is own thread
class EncodeThread : public QObject {
    Q_OBJECT

public:
    EncodeThread();

public slots:
    void encode();

signals:
    void encodeProgress(int i);
    void finished();

private:
    QThread myThread;
};

EncodeThread::EncodeThread() {
    moveToThread(&myThread);
    myThread.start();
}


void EncodeThread::encode()
{
    printf("EncodeThread::encode() on thread %u\n", (unsigned int) QThread::currentThreadId());

    for (int i = 0; i < 6; ++i) {
        // encode for 1 second or so
        printf("EncodeThread::encode() working on thread %u\n", (unsigned int) QThread::currentThreadId());
        Sleeper::sleep(1);
        emit encodeProgress(i);
    }

    emit finished();
    printf("EncodeThread::encode() - done\n");
}




// a controller to manage and monitor an EncoderThread instance
class VideoEncoderController : public QObject
{
    Q_OBJECT
public:
    void start();

public slots:
    void setValue(int);
    void encodingDone();

signals:
    void encodingBegin();
};

void VideoEncoderController::start()
{
    printf("VideoEncoderController::start() on thread %u\n", (unsigned int) QThread::currentThreadId());
    emit encodingBegin();
}

void VideoEncoderController::setValue(int x)
{
    printf("VideoEncoderController::setValue(int %d) on thread %u\n", x, (unsigned int) QThread::currentThreadId());
}

void VideoEncoderController::encodingDone()
{
    printf("VideoEncoderController::encodingDone() on thread %u\n", (unsigned int) QThread::currentThreadId());
}




// a demo program that wires up a VideoEncoderController object to
//  an EncoderThread
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    EncodeThread encThread;
    VideoEncoderController controller;

    QObject::connect(&encThread, SIGNAL(encodeProgress(int)), &controller, SLOT(setValue(int)));
    QObject::connect(&encThread, SIGNAL(finished()), &controller, SLOT(encodingDone()));
    QObject::connect(&controller, SIGNAL(encodingBegin()), &encThread, SLOT(encode()));

    printf("hello world on thread %u\n", (unsigned int) QThread::currentThreadId ());

    controller.start();

    return a.exec();
}



#include "main.moc"

Upvotes: 1

PeterSvP
PeterSvP

Reputation: 2046

You must derive QThread, not QObject. run() method is an abstract method of QThread.

Upvotes: 0

Related Questions