📜 ⬆️ ⬇️

Qt: template for correct work with threads

All habra
Somehow I needed to implement a multi-threaded application with intensive signal exchange in Qt 5.1.1 for WinXP in VS2009. I took Schlee , deducted from him that you need to inherit a class from QThread and - voila, well in multi-threading! Just in case, I looked into the Qt documentation - there no one objected to the inheritance from QThread of its class. Well - order, done! I start it - it seems like it works, but somehow it is not so ... I start tracking in debug mode - and there the devil knows what's going on! That signals do not go out, then go out, but somehow crookedly and from another stream. In short, a mess! I had to google thoroughly and sort out the topic (I was helped by the articles here , here and there ). As a result, I made a class template in C ++ (or rather, a whole hierarchy of these), which allowed me to finally write (relatively) small code of a class that lives in another thread that works correctly and stably.
Upd: in the comments I was prompted by a better approach - I described it in a new article .

What do you want


I strove for quite obvious things:

I got something like:

class SomeJob: public QObject { Q_OBJECT public: SomeJob () { /* ... */ } //   ~SomeJob () { /* ... */ } //   signals: void finished (); //     public slots: void to_terminate () { /* ... */ } //   }; ... ThreadedObject<SomeJob> thr; // -  thr.start (); //      


Beautiful!
')

How we will act


In Qt 5.1, the low-level QThread class is for our purposes. The following is said about him: “the QThread class gives the opportunity to manage threads in a platform-independent form”. Schlee had a wonderful book, but he came up with an example of threads that was very confusing: he offers to inherit the QThread class, override the run () method and do the work in it. This is not bad for a “launched, executed, and completed” task, but it is totally unacceptable for more complex cases.
In general, in vain I listened to him read, I had to immediately delve into the documentation. And there is, by the way, a good example. It shows the correct path: the QObject :: moveToThread function (QThread * thread) , which transfers affinity (similarity? - the affinity) of a given object and all its ancestors to the thread.
Thus, in the first approximation, the solution to the problem is as follows:
  1. thread creation - QThread class;
  2. creating an object and moving it to a new stream;
  3. installation of signal-slot communications;
  4. start a thread with a given priority.

It seems to be all right, but - remember? - I want the constructor of the created object to run in the new thread . To do this, it must be started after the thread starts . You can create an object first, and then a stream. But everything that will be created by the object constructor will be placed on the stack (heap) of the current thread, and not the new one. You can somehow neatly transfer all this household to a new stream and delete it in the old one, but ... it is easier to call the designer already in the new stream. So here we have the problem number 1. Need to solve.
Then came problem number 2. I created a nice template that inherits from QObject - I needed it for signal-slot communications. And then a pop-up appeared : “MOC does not allow using all the features of C ++. The main problem is that class templates cannot have signals or slots . ” # @ & *!
However, I also overcame this topic.

I came up with the following classes:
  1. your native class T;
  2. There is a class for creating an object - CreatorBase (a descendant of QObject). In the slot, it calls a new method to create a new object and transmits its address with a signal;
  3. there is a template implementation of the creator class - Creator <T> (descendant of CreatorBase). It implements a method for creating an object of a given type;
  4. There is a class called ThreadedObjectBase (a descendant of QObject) that creates a new thread. It receives the CreatorBase creator object and establishes the necessary signal-slot connections;
  5. the user uses the template object and thread storage class ThreadedObject <T> (a descendant of ThreadedObjectBase). It causes the creation of a new object and overloads the * and -> operators, as well as the type pointer of the object being created;
  6. the user creates a class (it can be a descendant of QObject) in which, if desired, it implements the “class finished work” signal and the “work interruption” slot, and can also specify a pending object deletion .


The sequence of actions turned out to be simple:
  1. the object and thread storage class is ThreadedObject;
  2. it creates the creator of the Creator and QThread objects for the new thread;
  3. the creator of the object is transferred to the new stream;
  4. signal-slot connections are established;
  5. the newly created thread starts up with the necessary priority;
  6. a custom class T is created in the created stream;
  7. ThreadedObjectBase finds out about this using the setObject slot (void * Obj), stores the address of the object and notifies the world about it with the signal objectIsReady ();
  8. You can learn about the successful ending of all these actions from the bool ThreadedObject <T> :: objectIsCreated (void) const.


Implementation


Consider the code of the created classes (so that it all fits into the screen, I removed the comments).
Object Creators:

 class CreatorBase: public QObject { Q_OBJECT void *_obj; protected: virtual void *Allocation (void) = 0; public slots: void allocate (void) { emit setObject (Allocation ()); } signals: void setObject (void *Obj); }; template <class T> class Creator: public CreatorBase { protected: void *Allocation (void) { return reinterpret_cast <void*> (new T); } }; 


Everything is obvious here: the CreatorBase creator base class has a allocate () slot that will run in the new active thread. It calls the setObject (void * Obj) signal, which passes the address of the object created in the descendant void * Creator <T> :: Allocation ().

The base class ThreadedObjectBase is as follows:

 class ThreadedObjectBase: public QObject { Q_OBJECT protected: QThread *_thread; virtual void SetObjectPointer (void *Ptr) = 0; ThreadedObjectBase (QObject *parent = 0): QObject (parent), _thread (0) {} void starting (CreatorBase *Creator, QThread::Priority Priority = QThread::InheritPriority, bool ToDeleteLaterThread = true) { bool res; _thread = new QThread; Creator->moveToThread (_thread); res = connect (_thread, SIGNAL (started ()), Creator, SLOT (allocate ())); Q_ASSERT_X (res, "connect", "connection is not established"); res = connect (Creator, SIGNAL (setObject (void*)), this, SLOT (setObject (void*))); Q_ASSERT_X (res, "connect", "connection is not established"); if (ToDeleteLaterThread) { res = connect (_thread, SIGNAL (finished ()), _thread, SLOT (deleteLater ())); Q_ASSERT_X (res, "connect", "connection is not established"); } _thread->start (Priority); } public: virtual ~ThreadedObjectBase (void) { } QThread *thread (void) { return _thread; } const QThread *cthread (void) const { return _thread; } signals: void objectIsReady (void); private slots: void setObject (void *Obj) { SetObjectPointer (Obj); emit objectIsReady (); } }; 


The basic method here is starting. It creates a thread with the specified priority. On start, the _thread thread calls QThread :: started () . We associate this signal with the CreatorBase :: allocate () slot, which creates a new object. That, in turn, causes the signal CreatorBase :: setObject (void *), which we pick up with the ThreadedObjectBase :: setObject (void * Obj) slot. Everything, the object is created (the signal ThreadedObjectBase :: objectIsReady () is issued about), a pointer to it is received.

If the user wants to set a deferred removal of the flow class (which is desirable), a connection is established inside _thread QThread :: finished () -> QObject :: deleteLater () .
Also, the user can set the signal name (will be stored in the _finished_signal variable). This signal is called by the created object at the end of its work. Similarly, the slot from _terminate_slot will be called by the flow interruption signal (the flow, however, does not stop instantaneously; you can wait for its termination by calling thread () -> wait - see QThread :: wait ).

Well, finally, the template class visible to the user:

 template <class T> class ThreadedObject: public ThreadedObjectBase { protected: T* _obj; Creator<T> _creator; const char *_finished_signal; const char *_terminate_slot; bool _to_delete_later_object; void SetObjectPointer (void *Ptr) { bool res; _obj = reinterpret_cast <T*> (Ptr); if (_finished_signal) { res = connect (_obj, _finished_signal, _thread, SLOT (quit ())); Q_ASSERT_X (res, "connect", "connection is not established"); } if (_terminate_slot) { res = connect (_thread, SIGNAL (finished ()), _obj, _terminate_slot); Q_ASSERT_X (res, "connect", "connection is not established"); } if (_to_delete_later_object && _finished_signal) { res = connect (_obj, _finished_signal, _obj, SLOT (deleteLater ())); Q_ASSERT_X (res, "connect", "connection is not established"); } } public: ThreadedObject (QObject *parent = 0): ThreadedObjectBase (parent), _obj (0) { } ~ThreadedObject (void) { } void start (const char *FinishedSignal = 0, const char *TerminateSlot = 0, QThread::Priority Priority = QThread::InheritPriority, bool ToDeleteLaterThread = true, bool ToDeleteLaterObject = true) { Creator<T> *creator = new Creator<T>; _finished_signal = FinishedSignal; _terminate_slot = TerminateSlot; _to_delete_later_object = ToDeleteLaterObject; starting (_creator, Priority, ToDeleteLaterThread); delete creator; } bool objectIsCreated (void) const { return _obj != 0; } T* ptr (void) { return reinterpret_cast <T*> (_obj); } const T* cptr (void) const { return reinterpret_cast <const T*> (_obj); } // .  operator T* (void) { return ptr (); } T* operator -> (void) { return ptr (); } operator const T* (void) const { return cptr (); } const T* operator -> (void) const { return cptr (); } }; 


Here the main method is start, which memorizes the names of signals and slots, and also sets the pending method deletion. The objectIsCreated () method returns true when an object has already been created. Multiple overloads allow you to use ThreadedObject <T> as a smart pointer.

Here is a simple example of using these classes:

 ThreadedObject <Operation> _obj; QObject::connect (&_obj, SIGNAL (objectIsReady ()), this, SLOT (connectObject ())); _obj.start (SIGNAL (finished ()), SLOT (terminate ()), QThread::HighPriority); 


A real example is attached below - a button is created in the main thread. In the new thread, an int variable is created, as well as a signal from the timer and a timer event. Both of these timers reduce the value of the variable int, when the value reaches zero, the QCoreApplication :: quit () slot is called. On the other hand, closing the application stops the flow. The example is verified in WinXP. I would like to hear in the comments about the successful trials in Linux, MacOS, Android and other supported platforms .

Example + Classes
ThreadedObject file:

 // ** // **      // ** class CreatorBase: public QObject { Q_OBJECT void *_obj; //   protected: virtual void *Allocation (void) = 0; //     public slots: void allocate (void) { emit setObject (Allocation ()); } //    signals: void setObject (void *Obj); //    }; // ** // **      // ** class ThreadedObjectBase: public QObject { Q_OBJECT protected: QThread *_thread; //  virtual void SetObjectPointer (void *Ptr) = 0; //     ThreadedObjectBase (QObject *parent = 0): QObject (parent), _thread (0) {} //   void starting (CreatorBase *Creator, QThread::Priority Priority = QThread::InheritPriority, bool ToDeleteLaterThread = true) //    { bool res; //    - _thread = new QThread; //   Creator->moveToThread (_thread); //  _creator   res = connect (_thread, SIGNAL (started ()), Creator, SLOT (allocate ())); Q_ASSERT_X (res, "connect", "connection is not established"); //   _thread    Creator- res = connect (Creator, SIGNAL (setObject (void*)), this, SLOT (setObject (void*))); Q_ASSERT_X (res, "connect", "connection is not established"); // Creat-    if (ToDeleteLaterThread) //   thread? { res = connect (_thread, SIGNAL (finished ()), _thread, SLOT (deleteLater ())); Q_ASSERT_X (res, "connect", "connection is not established"); } //   _thread     _thread->start (Priority); //   } public: // .  virtual ~ThreadedObjectBase (void) { } //    QThread *thread (void) { return _thread; } // ,   // .  const QThread *cthread (void) const { return _thread; } // ,   signals: void objectIsReady (void); //  " " private slots: void setObject (void *Obj) { SetObjectPointer (Obj); emit objectIsReady (); } //    }; // class ThreadedObjectBase // ** // **   // ** template <class T> class ThreadedObject: public ThreadedObjectBase { private: template <class T> class Creator: public CreatorBase //     { protected: void *Allocation (void) { return reinterpret_cast <void*> (new T); } }; protected: T* _obj; //  Creator<T> _creator; //   const char *_finished_signal; //  "  " const char *_terminate_slot; //  " " bool _to_delete_later_object; //  "  ? void SetObjectPointer (void *Ptr) //    { bool res; //    - _obj = reinterpret_cast <T*> (Ptr); //     if (_finished_signal) //   "  "? { res = connect (_obj, _finished_signal, _thread, SLOT (quit ())); Q_ASSERT_X (res, "connect", "connection is not established"); } //        if (_terminate_slot) //   " "? { res = connect (_thread, SIGNAL (finished ()), _obj, _terminate_slot); Q_ASSERT_X (res, "connect", "connection is not established"); } //        " " if (_to_delete_later_object && _finished_signal) //    ? { res = connect (_obj, _finished_signal, _obj, SLOT (deleteLater ())); Q_ASSERT_X (res, "connect", "connection is not established"); } //         } public: // .  ThreadedObject (QObject *parent = 0): ThreadedObjectBase (parent), _obj (0) {} //  ~ThreadedObject (void) { } //  void start (const char *FinishedSignal = 0, const char *TerminateSlot = 0, QThread::Priority Priority = QThread::InheritPriority, bool ToDeleteLaterThread = true, bool ToDeleteLaterObject = true) //    { _finished_signal = FinishedSignal; //    "  " _terminate_slot = TerminateSlot; //    " " _to_delete_later_object = ToDeleteLaterObject; //      starting (_creator, Priority, ToDeleteLaterThread); //   } // .  bool objectIsCreated (void) const { return _obj != 0; } //    ? T* ptr (void) { return reinterpret_cast <T*> (_obj); } //    const T* cptr (void) const { return reinterpret_cast <const T*> (_obj); } //     // .  operator T* (void) { return ptr (); } //    T* operator -> (void) { return ptr (); } //    operator const T* (void) const { return cptr (); } //     const T* operator -> (void) const { return cptr (); } //     }; // class ThreadedObject 


Main.cpp file:

 #include <QtGui> #include <QtWidgets> #include <QtCore> #include "ThreadedObject.h" // ** // **   // ** class Operation: public QObject { Q_OBJECT int *Int; //    QTimer _tmr; //  int _int_timer; //   public: Operation (void) { Int = new int (5); } //   ~Operation (void) { if (Int) delete Int; } //   signals: void addText(const QString &txt); //  " " void finished (); //  " " public slots: void terminate () //   { killTimer (_int_timer); //    _tmr.stop (); //    delete Int; //   Int = 0; //    emit finished (); //    } void doAction (void) //   { bool res; emit addText (QString ("- %1 -"). arg (*Int)); res = QObject::connect (&_tmr, &QTimer::timeout, this, &Operation::timeout); Q_ASSERT_X (res, "connect", "connection is not established"); //    _tmr.start (2000); //    thread()->sleep (1); //  1 ... timeout (); // ...   ... startTimer (2000); // ...     } protected: void timerEvent (QTimerEvent *ev) { timeout (); } //   private slots: void timeout (void) { if (!Int || !*Int) //  ? return; // ...  --*Int; //   emit addText (QString ("- %1 -"). arg (*Int)); //   if (!Int || !*Int) //  ? emit finished (); // ...  } }; // ** // ** ,    // ** class App: public QObject { Q_OBJECT ThreadedObject <Operation> _obj; // - QPushButton _btn; //  protected: void timerEvent (QTimerEvent *ev) { bool res; //    - killTimer (ev->timerId ()); //   res = QObject::connect (&_obj, SIGNAL (objectIsReady ()), this, SLOT (connectObject ())); Q_ASSERT_X (res, "connect", "connection is not established"); //     _obj.start (SIGNAL (finished ()), SLOT (terminate ()), QThread::HighPriority); //      } private slots: void setText (const QString &txt) { _btn.setText (txt); } //     void connectObject (void) //     { bool res; //    - res = QObject::connect (this, &App::finish, _obj, &Operation::terminate); Q_ASSERT_X (res, "connect", "connection is not established"); //        res = QObject::connect (this, &App::startAction, _obj, &Operation::doAction); Q_ASSERT_X (res, "connect", "connection is not established"); //     res = QObject::connect (_obj, &Operation::finished, this, &App::finish); Q_ASSERT_X (res, "connect", "connection is not established"); //      res = QObject::connect (_obj, &Operation::addText, this, &App::setText); Q_ASSERT_X (res, "connect", "connection is not established"); //     res = QObject::connect (&_btn, &QPushButton::clicked, _obj, &Operation::terminate); Q_ASSERT_X (res, "connect", "connection is not established"); //    _btn.show (); //   emit startAction (); //   } public slots: void terminate (void) { emit finish (); } //    signals: void startAction (void); //  " " void finish (void); //  " " }; // ** // **     // ** int main (int argc, char **argv) { QApplication app (argc, argv); //  App a; //  bool res; //    a.startTimer (0); //          res = QObject::connect (&a, SIGNAL (finish ()), &app, SLOT (quit ())); Q_ASSERT_X (res, "connect", "connection is not established"); //      res = QObject::connect (&app, SIGNAL (lastWindowClosed ()), &a, SLOT (terminate ())); Q_ASSERT_X (res, "connect", "connection is not established"); //      return app.exec(); //     } #include "main.moc" 

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


All Articles