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~~~
-
-
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~~~
@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 theImageProvider
when in need for a new image and then connect those signals to theWorker
object's slots. -
-
@kshegunov
Got it. Thanks for your kindness and patience.