Few questions about QThread worker-object model
Unsolved
General and Desktop
-
I haven't used threads too extensively and just want to clarify a few things.
Assuming a setup like this:
class Worker : public QObject { Q_OBJECT std::shared_ptr<LargeObject> mObjectToWorkOn; public: Worker(std::shared_ptr<LargeObject> workable) : mObjectToWorkOn(workable) {} public slots: void doWork() { // Large operation start mObjectToWorkOn->doSomething(); // Large operation end emit resultReady(result); } signals: void resultReady(); }; class MainWindow : public QMainWindow { Q_OBJECT std::shared_ptr<LargeObject> mWorkableObject; QThread mWorkerThread; public: MainWindow () { // Setup UI.... // At some point initialize this, actually in a MainWindow slot but showing here for simplicity workableObject = std::makeShared<LargeObject>(...); } ~Controller() { // From the QThread docs: What exactly does this do workerThread.quit(); workerThread.wait(); } private: void doLongJob() { Worker *worker = new Worker(mWorkableObject); worker->moveToThread(&mWorkerThread); connect(&mWorkerThread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &MainWindow ::operate, worker, &Worker::doWork); connect(worker, &Worker::resultReady, this, &MainWindow ::handleResults); mWorkerThread.start(); //worker.doWork() is this allowed? connect(mWorkerThread, SIGNAL(started()), worker, SLOT(doHeavyCaclulations()));; // Otherwise, assuming I want to start the work immediately this is fine right? Obviously I would place this before calling start() } public slots: void handleResults() { // Continue program tasks in main thread } signals: void operate(); };
While I'm sure my questions technically can't be answered with 100% certainty unless you know exactly how LargeObject is setup, here is the general premise:
- mObjectToWorkOn will never be null when a Worker instance is created
- mObjectToWorkOn will never be modified or even inspected by the main thread (user code anyway) while a worker instance is being used
- LargeObject::doSomething() (and all its other code) only: interacts with its own members, performs some file IO with QtSql and QtXML
- I will never be using more than one Worker instance at a time and mWorkerThread will be only other thread in my code other than the main/UI thread
Questions:
- Is it ok to use shared_ptr like this assuming the above? LargeObject (hence its name) has too many variables and complex methods I need to use for its data to simply be extracted and copied into the Worker and then passed back.
- If I always want to start the Worker right after creating it, can I simply just do worker->doWork() after moving it to the other thread? Or must I call that using a signal as shown due to how QThreads are handled internally
- Will the operations I perform on the shared instance of LargeObject within Worker::doWork() still be performed within the secondary (mWorkerThread) thread even though the instance was initially created in the main thread?
- Is
connect(&mWorkerThread, &QThread::finished, worker, &QObject::deleteLater)
sufficient for ensuring the Worker is destroyed and cleaned up correctly after resultReady() is emitted and the worker idles? Or are there other factors I need to consider? - When is mWorkerThread's QThread::finished() emitted here? I essentially want it to be "finished" after the worker emits resultReady() (at which point no more user code will be run, i.e. it will be the last thing I call in doWork() ), but the user cannot emit this signal manually.
- If finished() is emitted automatically by somehow detecting the QThread no longer has any workers it owns that are doing anything, if I later wanted the worker to have two things to do at separate times via a second function (i.e. doMoreWork() ), how do I prevent QThread from emitting finished() too early?
- Once one instance of Worker has been passed, executed, and the secondary thread finishes, if at a later time I wish to do the long operation again using a new Worker, can I simply call doLongJob() again let a new Worker be moved into the thread? Or do I need to "reset" the thread or perform other kind of maintenance between each usage?
- In this model, how do I cleanup mWorkerThread when I am finished with it? I want it to remain throughout the lifetime of MainWindow so I would do any cleanup in its destructor... which on that note:
- What exactly is happening in the destructor from the docs example as shown above? I don't believe I'll need an event loop within mWorkerThread via exec() so I don't need to call quit() right? Is the call to wait() just to make sure the thread is frozen and not doing anything before it is deleted due to going out of scope when MainWindow is destroyed (i.e. a safety thing)? Do I actually need to do that if I know for certain no workers will remain when MainWindow is deleted?
At the moment this is just so a ProgressDialog running in the UI thread won't lag (the operations I'm doing sometimes have long periods of time between calls to setValue and I want to avoid spamming QApplication::processEvents()), but I will likely expand on its functionality later.