Multithread architecture



  • Hi.

    I have a problem about multithreading architecture, really need you professional suggestions.

    I am using QT5.7 with QML. Now I have five files and wondering what is the right way to design the architecture:

    • main.cpp

    • main.qml

    • worker.h and worker.cpp - communicate with main.qml

    • imgProcessing.h and imgProcessing.cpp - comunicate with worker.h/cpp .......... hope it could be in another thread

    • imgProvider.h and imgProvider.cpp - communicate with worker.h/cpp, inherited from QQuickImageProvider

    A simple work flow would be:

    • main.qml receive an UI event and call corresponding function in worker

    • worker call the functions in imgProcessing to do some image processing

    • the resulted image return to worker

    • In order to display the resulted image, worker send the image to imgProvider

    • In order to show the image, worker emit an signal to main.qml to change the &id of the image source, so that requestPixmap() method in imgProvider can be invoked to return the image.

    I want to put the imgProcessing.h/cpp functions in another thread so that it will not block the action on UI. More specifically, I hope the image update by imgProvider.h/cpp and image processing steps in imgProcessing.h/cpp can be in different thread. Are there any better architecture for this requirement ? Actually I failed to implement it. Below is the main.cpp:

    int main(int argc, char *argv[])
    {
        QGuiApplication app(argc, argv);
    
        qDebug()<<"From main thread: "<<QThread::currentThreadId();
        QThread t;
    
        ImageProvider *provider = new ImageProvider(0);
    
        Worker *worker = new Worker(provider);
    
        worker->moveToThread(&t);
    
        t.start();
    
        QQmlApplicationEngine engine;
    
        engine.addImageProvider(QLatin1String("colors"), provider);
    
        QQmlContext *ctx = engine.rootContext();
        ctx->setContextProperty("WorkerContext", worker);
    
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        return app.exec();
    
    }
    

    And here is the error message:

    QML debugging is enabled. Only use this in a safe environment.
    From main thread:  0x7fff7bb01000
    Worker::Worker
    QQmlEngine: Illegal attempt to connect to Worker(0x7f8e4af7d820) that is in a different thread than the QML engine            
    QQmlApplicationEngine(0x7fff53ac8b00.
    The program has unexpectedly finished.
    

    Thanks for reading up such a long question, I wonder:

    • Is this architecture correct?

    • If so, how to fix the error to make it work?

    Thanks~~~


  • Qt Champions 2016

    @VincentLiu said in Multithread architecture:

    As the message says you can't do:

    ctx->setContextProperty("WorkerContext", worker);
    

    when worker is in a different thread, it's not allowed. What you should do instead is have signals and slots from your worker to another object that is the context, which lives in the QML's thread.
    Here's a very simple (incomplete) example of how you should do it:

    class WorkerContext : pubic QObject
    {
        Q_OBJECT
    
    public:
        WorkerContext(QObject * parent = NULL)
            : QObject(parent)
        {
        }
    
        Q_INVOKABLE void requestImage()
        {
            emit imageNeeded();
        }
    
    signals:
        void imageNeeded();
        void imageReady(const QImage & image);
    };
    

    Then connect stuff together:

    int main(int argc, char *argv[])
    {
        QGuiApplication app(argc, argv);
    
        // Create the worker object
        QThread workerThread;
    
        // When the application is quitting, so should the worker thread
        QObject::connect(&app, &QCoreApplication::aboutToQuit, &workerThread, &QThread::quit);
    
        Worker * workerObject = new Worker;
        workerObject->moveToThread(&workerThread);
    
        workerThread.start();
    
        // Connect the worker object with the QML context object
        WorkerContext * workerContext = new WorkerContext;
        QObject::connect(workerContext, &WorkerContext::imageNeeded, workerObject, &Worker::processImage);
        QObject::connect(workerObject, &Worker::imageReady, workerContext, &WorkerContext::imageReady);
    
        QQmlApplicationEngine engine;
    
        ImageProvider * provider = new ImageProvider;
        engine.addImageProvider(QLatin1String("colors"), provider);
    
        QQmlContext * ctx = engine.rootContext();
        ctx->setContextProperty("WorkerContext", workerContext);
    
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        int result = app.exec();
    
        // You need to wait for the worker thread to exit gracefully
        workerThread.wait();
    
        // Exit the main thread only when the worker thread has finished
        return result;
    }
    

    You can call the context obejct's methods as usual from QML:

    WorkerContext.requestImage(); //< Need an image
    

    and connect the WorkerContext.imageReady signal to your QML code.

    However you may not need the WorkerContext class here at all, you can raise the signals from the ImageProvider when in need for a new image and then connect those signals to the Worker object's slots.



  • @kshegunov
    Got it. Thanks for your kindness and patience.


Log in to reply
 

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