📜 ⬆️ ⬇️

Proper use of QThread

In a recent project with Qt, I had to deal with the QThread class. As a result, I came up with the “right” technology of working with QThread, which I will use in other projects.

Task


There is a service that monitors a directory with a rather large number of files, from hundreds to thousands. Based on the analysis of the contents of the files, reports are generated (up to five reports of different types). If the content of a file changes, or the number of files changes, report building is interrupted and starts anew. Report building functionality is implemented in the ReportBuilder class. Each type of report uses its own class, inherited from ReportBuilder . Reports should preferably be built in parallel threads.

Examples in Qt documentation: wrong


I started by reading the documentation and examples of Qt. In all examples, a thread is created by inheriting the QThread class and overriding the run() method:
 class MyThread : public QThread { Q_OBJECT protected: void run(); }; void MyThread::run() { ... } 


Along the way, I read a post in which the developer of Qt Bradley T.Hughes claims that the inheritance of QThread only for executing class code in a separate thread is a completely wrong idea:
“It’s not a problem.” We are an object-oriented programmers subclass because we want to extend or specialize the base class functionality. If you’re not happy, you can’t get it. This is a subclass of QThread; it should be encapsulated. ”
')
“The QThread class has been created and is intended to be used as an interface to operating system threads, but not to put code intended for execution in a separate thread. In OOP, we inherit a class in order to extend or deepen the functionality of the base class. The only justification for QThread inheritance I can imagine is adding functionality that does not exist in QThread, for example, passing a pointer to a memory region that a thread can use for its stack, or perhaps adding support for real-time interfaces. Uploading files, working with databases, and similar functions should not be present in the inherited QThread classes; they must be implemented in other objects ”

Those. inheritance from QThread is not that completely wrong, but leads to unnecessary mixing of different sets of functions in the same class, which impairs readability and maintainability of the code. But, if QThread is inherited incorrectly, then how is it correct? After a short search, I found this post , in which everything is laid out on the shelves. Key points of the post:
  1. QThread is not a stream, but Qt is a wrapper for a stream of a specific OS, which allows you to interact with the stream from the Qt project, primarily through Qt signals / slots.
  2. The allocation of memory by the new operator to class instances intended for execution in a separate thread should be already in the stream. The owner of the object will be the stream that allocated memory to the object.
  3. To manage flows and objects “living” in them, it is important to correctly configure messaging.


How to



So the "correct" recipe for starting and stopping classes in streams:

Create a wrapper for a class that will live in a separate thread. In our case, this is ReportBuilder . Wrap for him: RBWorker .
 class RBWorker : public QObject { Q_OBJECT private: ReportBuilder *rb; /*   */ QStringList file_list; /*     */ ReportType r_type; /*   */ public: RBWorker(ReportType p_type ); ~RBWorker(); void setFileList(const QStringList &files) { file_list = files; } /*      */ public slots: void process(); /*      */ void stop(); /*    */ signals: void finished(); /*       */ }; RBWorker:: RBWorker (ReportType p_type) { rb = NULL; r_type = p_type; } RBWorker::~ RBWorker () { if (rb != NULL) { delete rb; } } void RBWorker::process() { if(file_list.count() == 0) { emit finished(); return; } switch (r_type) { case REPORT_A: { rb = new ReportBuilderA (); break; } case REPORT_B: { rb = new ReportBuilderB (); break; } case REPORT_C: { rb = new ReportBuilderC (); break; } default: emit finished(); return ; } } rb->buildToFile(file_list); /*  buildToFile   rb->stop() */ emit finished(); return ; } void RBWorker::stop() { if(rb != NULL) { rb->stop(); } return ; } 


An important point: a ReportBuilder instance is created in the process(), method process(), and not in the RBWorker constructor.

The Session class tracks changes in files and starts building reports.

 class Session : public QObject { Q_OBJECT public: Session(QObject *parent, const QString &directory, const QVector<ReportType> &p_rt); ~Session(); void buildReports(); private: void addThread(ReportType r_type); void stopThreads(); QStringList files; QVector<ReportType> reports; //  signals: void stopAll(); //   }; 


The most important method in class: addThread

 void Session::addThread(ReportType r_type) { RBWorker* worker = new RBWorker(r_type); QThread* thread = new QThread; worker->setFileList(files); /*      */ worker->moveToThread(thread); /*     . : */ connect(thread, SIGNAL(started()), worker, SLOT(process())); /* …        process(),    ,       : */ connect(worker, SIGNAL(finished()), thread, SLOT(quit())); /* …      ,      finished() ,    quit() : */ connect(this, SIGNAL(stopAll()), worker, SLOT(stop())); /* …  Session         ,         finished()  : */ connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater())); /* …           : */ connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); /* …      ,    .        .   : */ thread->start(); /*  ,   RBWorker::process(),   ReportBuilder     */ return ; } void Session::stopThreads() /*     */ { emit stopAll(); /*  RBWorker   ,        quit()   */ } void Session::buildReports() { stopThreads(); for(int i =0; i < reports.size(); ++i) { addThread(reports.at(i)); } return ; } void Session::~Session() { stopThreads(); /*         */ … } 


In practice, everything worked almost immediately. Many thanks to Maya Posch - helped to figure it out.

Source: https://habr.com/ru/post/150274/


All Articles