सभी को नमस्कार!
किसी तरह मुझे VS2009 में WinXP के लिए Qt 5.1.1 में गहन सिग्नल एक्सचेंज के साथ एक मल्टीथ्रेडेड एप्लिकेशन को लागू करने की आवश्यकता थी। मैंने
शेली लिया, उससे घटाया कि मुझे
क्यूथ्रेड और वॉइला से वर्ग को विरासत में प्राप्त करने की आवश्यकता थी, अच्छी तरह से मल्टीथ्रेडिंग में! बस मामले में, मैंने
क्यूटी प्रलेखन में देखा - कोई भी इसके वर्ग को विरासत में देने के लिए आपत्ति नहीं करता था। अच्छा - हुक्म हुआ! मैं इसे शुरू करता हूं - ऐसा लगता है कि यह काम करता है, लेकिन किसी तरह ऐसा नहीं है ... मैं डिबग मोड में ट्रैक करना शुरू करता हूं - और शैतान जानता है कि वहां क्या चल रहा है! या तो सिग्नल बाहर नहीं निकलते हैं, फिर वे बाहर निकल जाते हैं, लेकिन किसी तरह टेढ़े और दूसरी धारा से। एक शब्द में, एक पूरी गड़बड़! मुझे विषय को अच्छी तरह से गूगल करना और समझना था (
यहाँ लेख,
यहाँ और
वहाँ मेरी मदद की)। नतीजतन, मैंने सी ++ (या बल्कि, इन की एक पूरी पदानुक्रम) में एक क्लास टेम्पलेट बनाया, जिसने मुझे एक और धागे में रहने वाले एक वर्ग के लिए (अपेक्षाकृत) छोटे कोड को लिखने की अनुमति दी जो सही और stably काम करता है।
अद्यतन: टिप्पणियों में उन्होंने बेहतर दृष्टिकोण का सुझाव दिया - मैंने इसे एक नए लेख में वर्णित किया ।
तुम क्या चाहते हो
मैं काफी स्पष्ट चीजों के लिए प्रयास करता हूं:
- C ++ कक्षाओं को उनके सभी महिमा में उपयोग करें - अर्थात, निर्माणकर्ता तब कहा जाता है जब धारा बनाई जाती है और विनाश से पहले विध्वंसक;
- Qt की क्षमताओं का उपयोग इसकी सभी महिमा में - अर्थात, सिग्नल-स्लॉट कनेक्शन, ईवेंट, आदि;
- यदि वांछित है - निर्माण और काम की प्रक्रिया पर नियंत्रण - प्रवाह की प्राथमिकता, काम की शुरुआत के बारे में एक स्लॉट और आपातकालीन समापन के बारे में एक संकेत;
- न्यूनतम स्क्रिबल और अधिकतम स्पष्टता।
मुझे कुछ ऐसा मिला:
class SomeJob: public QObject { Q_OBJECT public: SomeJob () { /* ... */ } // ~SomeJob () { /* ... */ } // signals: void finished (); // public slots: void to_terminate () { /* ... */ } // }; ... ThreadedObject<SomeJob> thr; // - thr.start (); //
Krasoten!
हम कैसे कार्य करेंगे
Qt 5.1 में, निम्न-स्तर के qhread वर्ग का उद्देश्य हमारे उद्देश्यों के लिए है।
निम्नलिखित उसके बारे
में कहा गया है : "क्यूथ्रेड वर्ग एक प्लेटफ़ॉर्म-स्वतंत्र तरीके से थ्रेड्स को प्रबंधित करने की क्षमता प्रदान करता है।" शेली के पास एक अद्भुत पुस्तक है, लेकिन यहां थ्रेड्स के साथ एक उदाहरण सामने आया है जो बहुत भ्रामक है: वह क्यूथ्रेड क्लास को
विरासत में
लेने का सुझाव देता है, रन () विधि को ओवरराइड करता है और इसमें काम करता है। यह "शुरू, एक कार्य निष्पादित और पूर्ण" की शैली में एक कार्य के लिए बुरा नहीं है, लेकिन अधिक जटिल मामलों के लिए स्पष्ट रूप से अस्वीकार्य है।
सामान्य तौर पर, व्यर्थ में मैंने
इसे सुना और इसे पढ़ा, मुझे तुरंत प्रलेखन में तल्लीन करना पड़ा। और यह एक अच्छा उदाहरण है। यह सही रास्ता दिखाता है:
QObject :: MoveToThread (QThread * thread) फ़ंक्शन , जो इस ऑब्जेक्ट के
आत्मीयता (समानता? - अंग्रेजी आत्मीयता) को स्थानांतरित करता है और इसके सभी पूर्वजों को थ्रेड थ्रेड के लिए।
इस प्रकार, पहले सन्निकटन में, समस्या का समाधान निम्नानुसार है:
- धागा निर्माण - क्यूथ्रेड वर्ग;
- एक वस्तु बनाना और उसे एक नई धारा में स्थानांतरित करना;
- सिग्नल-स्लॉट संचार की स्थापना;
- किसी प्राथमिकता के साथ एक स्ट्रीम शुरू करना।
सब कुछ ठीक लगता है, लेकिन - याद है? - मैं चाहता हूं कि बनाई गई वस्तु का निर्माता
नए धागे में निष्पादित हो। ऐसा करने के लिए,
धागा शुरू होने के बाद इसे शुरू करना होगा। आप पहले एक ऑब्जेक्ट बना सकते हैं, और फिर एक स्ट्रीम। लेकिन जो कुछ भी ऑब्जेक्ट के निर्माता द्वारा बनाया जाएगा, उसे
वर्तमान धागे के ढेर (ढेर) पर रखा जाएगा, और नया नहीं। आप किसी भी तरह बड़े करीने से इस सभी अर्थव्यवस्था को एक नए धागे में स्थानांतरित कर सकते हैं और इसे पुराने में हटा सकते हैं, लेकिन ... नए धागे में पहले से ही निर्माता को कॉल करना आसान है। तो यहाँ हम समस्या नंबर 1 है। यह तय करना आवश्यक है।
तब समस्या नंबर 2 थी। मैंने एक अच्छा खाका तैयार किया जो QObject से विरासत में मिला है - मुझे सिग्नल-स्लॉट संचार के लिए इसकी आवश्यकता थी। और फिर एक
बायक सामने आया: “MOC आपको C ++ की सभी विशेषताओं का उपयोग करने की अनुमति नहीं देता है। मुख्य समस्या यह है कि
क्लास टेम्प्लेट में सिग्नल या स्लॉट नहीं हो सकते हैं । " # # @ *!
हालाँकि, मैंने इस विषय को भी पछाड़ दिया।
मैं निम्नलिखित वर्गों के साथ आया:
- आपका मूल वर्ग T है;
- एक ऑब्जेक्ट क्रिएशन क्लास है - क्रिएटरबेस (QObject का वंशज)। एक आभासी विधि को कॉल करके, वह स्लॉट में एक नई वस्तु बनाता है और एक सिग्नल के साथ अपना पता प्रसारित करता है;
- निर्माता वर्ग का एक टेम्पलेट कार्यान्वयन है - निर्माता <T> (निर्माता का एक वंशज)। यह किसी दिए गए प्रकार की वस्तु बनाने की एक विधि को लागू करता है;
- एक थ्रेडऑबजेक्टबेस क्लास (QObject का वंशज) है जो एक नया थ्रेड बनाता है। यह CreatorBase निर्माता को प्राप्त करता है और आवश्यक सिग्नल-स्लॉट संचार स्थापित करता है;
- उपयोगकर्ता ऑब्जेक्ट और स्ट्रीम थ्रेडऑब्जेक्ट <T> (थ्रेडऑबजेक्टबसे का वंशज) के टेम्पलेट स्टोरेज क्लास का उपयोग करता है। यह एक नई वस्तु के निर्माण को बुलाता है और * और -> ऑपरेटरों को अधिभारित करता है, साथ ही साथ बनाई गई वस्तु का प्रकार सूचक;
- उपयोगकर्ता एक वर्ग बनाता है (वह QObject का वंशज हो सकता है), जिसमें वह वैकल्पिक रूप से सिग्नल "क्लास समाप्त काम" और स्लॉट "काम में रुकावट" को लागू करता है, और ऑब्जेक्ट को हटाने को स्थगित भी कर सकता है।
क्रियाओं का क्रम सरल हुआ:
- ऑब्जेक्ट का संग्रहण वर्ग और थ्रेडेडबजेक्ट स्ट्रीम का उपयोग किया जाता है;
- वह नए धागे के लिए निर्माता और क्यूथ्रेड ऑब्जेक्ट के निर्माता बनाता है;
- ऑब्जेक्ट के निर्माता को एक नए थ्रेड में स्थानांतरित किया जाता है;
- सिग्नल-स्लॉट संचार स्थापित किए जाते हैं;
- एक नया बनाया धागा आवश्यक प्राथमिकता से शुरू होता है;
- एक कस्टम वर्ग T निर्मित थ्रेड में बनाया गया है;
- ThreadedObjectBase इस बारे में सीखता है setObject (void * Obj) स्लॉट का उपयोग करते हुए, ऑब्जेक्ट के पते को याद रखता है और सिग्नल ऑब्जेक्टIsReady () का उपयोग करके इसके बारे में दुनिया को सूचित करता है;
- इन सभी कार्यों के सफल अंत के लिए, बूल थ्रेडऑब्जेक्ट देखें <T> :: objectIsCreated (शून्य)।
कार्यान्वयन
बनाई गई कक्षाओं के कोड पर विचार करें (ताकि यह सभी स्क्रीन में फिट हो, मैंने टिप्पणियां हटा दीं)।
ऑब्जेक्ट निर्माता:
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); } };
यहां सब कुछ स्पष्ट है: CreatorBase निर्माता बेस क्लास में एक आवंटित () स्लॉट है, जिसे नए सक्रिय थ्रेड में लॉन्च किया जाएगा। यह सिग्नल सेटऑबजेक्ट (शून्य * ओबज) को कॉल करता है, जो बच्चे के शून्य में बनाए गए ऑब्जेक्ट के पते को पास करता है * क्रिएटर <टी> :: आवंटन ()।
बेस क्लास थ्रेडेडबजेक्टबेस इस प्रकार है:
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 (); } };
यहां मुख्य विधि शुरू हो रही है। यह निर्दिष्ट प्राथमिकता के साथ एक धागा बनाता है। जब लॉन्च किया गया, तो _thread थ्रेड QThread
:: start
() सिग्नल कहता है। हम इस सिग्नल को स्लॉट CreatorBase :: आवंटित () के साथ जोड़ते हैं, जो एक नई वस्तु बनाता है। यह, बदले में, सिग्नल CreatorBase :: setObject (शून्य *) का कारण बनता है, जिसे हम थ्रेडेड ओब्जेक्टBase :: setObject (शून्य * ओबज) स्लॉट के साथ उठाते हैं। सब कुछ, ऑब्जेक्ट बनाया गया है (जिसके बारे में सिग्नल थ्रेडेडबजेक्टबेसे :: objectIsReady () जारी किया गया है), इसके लिए एक संकेतक प्राप्त होता है।
यदि उपयोगकर्ता थ्रेड क्लास (जो वांछनीय है) को हटाने में देरी करना चाहता है, तो कनेक्शन _thread qhread
:: समाप्त () ->
QObject :: deleteLater () के अंदर स्थापित किया गया है।
इसके अलावा, उपयोगकर्ता सिग्नल का नाम सेट कर सकता है (यह वैरिएबल _finished_signal में संग्रहीत किया जाएगा)। यह संकेत अपने काम के अंत में बनाई गई वस्तु द्वारा कहा जाता है। इसी तरह, _terminate_slot से स्लॉट को एक थ्रेड रुकावट संकेत (थ्रेड, हालांकि, तुरंत बंद नहीं होगा) कहा जाएगा; आप इसे थ्रेड कॉल करके समाप्त होने की प्रतीक्षा कर सकते हैं () -> प्रतीक्षा - देखें
qhread :: Wait )।
और अंत में, उपयोगकर्ता को दिखाई देने वाला टेम्पलेट वर्ग:
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); }
यहां, मुख्य विधि शुरू होती है, जो सिग्नल और स्लॉट्स के नामों को याद करती है, और विधि को हटाने के लिए स्थगित भी करती है। जब ऑब्जेक्ट पहले से ही बनाया गया है, तो ObjectICreated () विधि सही है। एकाधिक अधिभार आपको थ्रेडेडबजेक्ट <T> को स्मार्ट पॉइंटर के रूप में उपयोग करने की अनुमति देते हैं।
इन वर्गों का उपयोग करने का एक सरल उदाहरण यहां दिया गया है:
ThreadedObject <Operation> _obj; QObject::connect (&_obj, SIGNAL (objectIsReady ()), this, SLOT (connectObject ())); _obj.start (SIGNAL (finished ()), SLOT (terminate ()), QThread::HighPriority);
एक वास्तविक उदाहरण नीचे संलग्न है - मुख्य धागे में एक बटन बनाया गया है। नए थ्रेड में एक इंट चर बनाया जाता है, साथ ही टाइमर और एक टाइमर घटना से संकेत भी। ये दोनों टाइमर इंट वेरिएबल के मूल्य को कम करते हैं, शून्य पर पहुंचने पर,
QCoreApplication :: Skip () स्लॉट कहलाता है। दूसरी ओर, एप्लिकेशन को बंद करना, थ्रेड को रोकता है। WinXP में उदाहरण का परीक्षण किया गया। मैं लिनक्स, मैकओएस, एंड्रॉइड और
अन्य समर्थित प्लेटफार्मों में सफल परीक्षणों के बारे में टिप्पणियों में सुनना चाहूंगा।
उदाहरण + कक्षाएंथ्रेडऑबजेक्ट फ़ाइल:
Main.cpp फ़ाइल:
#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"