นี่คือ ตัวอย่างหนึ่งของ วิธีใช้ QThread อย่างถูกต้อง แต่มีปัญหาบางประการ ซึ่งแสดงอยู่ในความคิดเห็น โดยเฉพาะอย่างยิ่ง เนื่องจากลำดับในการดำเนินการสล็อตไม่ได้ถูกกำหนดไว้อย่างเคร่งครัด จึงอาจทำให้เกิดปัญหาต่างๆ ได้ ความคิดเห็นที่โพสต์เมื่อวันที่ 6 สิงหาคม 2013 ให้แนวคิดที่ดีว่าจะจัดการกับปัญหานี้อย่างไร ฉันใช้อะไรทำนองนั้นในโปรแกรมของฉัน และนี่คือโค้ดตัวอย่างบางส่วนเพื่อชี้แจง
แนวคิดพื้นฐานก็เหมือนกัน: ฉันสร้างอินสแตนซ์ QThread ที่อยู่ในเธรดหลักของฉัน อินสแตนซ์คลาสผู้ปฏิบัติงานที่อยู่ในเธรดใหม่ที่ฉันสร้างขึ้น จากนั้นฉันจะเชื่อมต่อสัญญาณทั้งหมด
void ChildProcesses::start()
{
QThread *childrenWatcherThread = new QThread();
ChildrenWatcher *childrenWatcher = new ChildrenWatcher();
childrenWatcher->moveToThread(childrenWatcherThread);
// These three signals carry the "outcome" of the worker job.
connect(childrenWatcher, SIGNAL(exited(int, int)),
SLOT(onChildExited(int, int)));
connect(childrenWatcher, SIGNAL(signalled(int, int)),
SLOT(onChildSignalled(int, int)));
connect(childrenWatcher, SIGNAL(stateChanged(int)),
SLOT(onChildStateChanged(int)));
// Make the watcher watch when the thread starts:
connect(childrenWatcherThread, SIGNAL(started()),
childrenWatcher, SLOT(watch()));
// Make the watcher set its 'stop' flag when we're done.
// This is performed while the watch() method is still running,
// so we need to execute it concurrently from this thread,
// hence the Qt::DirectConnection. The stop() method is thread-safe
// (uses a mutex to set the flag).
connect(this, SIGNAL(stopped()),
childrenWatcher, SLOT(stop()), Qt::DirectConnection);
// Make the thread quit when the watcher self-destructs:
connect(childrenWatcher, SIGNAL(destroyed()),
childrenWatcherThread, SLOT(quit()));
// Make the thread self-destruct when it finishes,
// or rather, make the main thread delete it:
connect(childrenWatcherThread, SIGNAL(finished()),
childrenWatcherThread, SLOT(deleteLater()));
childrenWatcherThread->start();
}
พื้นหลังบางส่วน:
คลาส ChildProcesses เป็นตัวจัดการกระบวนการลูกที่เริ่มกระบวนการลูกใหม่ด้วยการเรียก spawn() เก็บรายการกระบวนการที่กำลังทำงานอยู่และอื่นๆ อย่างไรก็ตาม จำเป็นต้องติดตามสถานะลูก ซึ่งหมายถึงการใช้การเรียก waitpid() บน Linux หรือ WaitForMultipleObjects บน Windows ฉันเคยเรียกสิ่งเหล่านี้ในโหมดไม่บล็อกโดยใช้ตัวจับเวลา แต่ตอนนี้ฉันต้องการการตอบสนองที่รวดเร็วยิ่งขึ้น ซึ่งหมายถึงโหมดการบล็อก นั่นคือสิ่งที่ด้ายเข้ามา
คลาส ChildrenWatcher มีการกำหนดไว้ดังนี้:
class ChildrenWatcher: public QObject {
Q_OBJECT
private:
QMutex mutex;
bool stopped;
bool isStopped();
public:
ChildrenWatcher();
public slots:
/// This is the method which runs in the thread.
void watch();
/// Sets the stop flag.
void stop();
signals:
/// A child process exited normally.
void exited(int ospid, int code);
/// A child process crashed (Unix only).
void signalled(int ospid, int signal);
/// Something happened to a child (Unix only).
void stateChanged(int ospid);
};
นี่คือวิธีการทำงาน เมื่อสิ่งเหล่านี้เริ่มต้นขึ้น ระบบจะเรียกเมธอด ChildProcess::start() (ดูด้านบน) มันสร้าง QThread ใหม่และ ChildrenWatcher ใหม่ ซึ่งจากนั้นจะถูกย้ายไปยังเธรดใหม่ จากนั้น ฉันเชื่อมโยงสัญญาณสามประการที่แจ้งให้ผู้จัดการของฉันทราบเกี่ยวกับชะตากรรมของกระบวนการย่อย (ออก/ส่งสัญญาณ/พระเจ้ารู้ดีว่าเกิดอะไรขึ้น) จากนั้นเริ่มความสนุกหลัก
ฉันเชื่อมต่อ QThread::started() กับเมธอด ChildrenWatcher::watch() ดังนั้นจึงเริ่มต้นทันทีที่เธรดพร้อม เนื่องจากผู้ดูอาศัยอยู่ในเธรดใหม่ นั่นคือที่ที่เมธอด watch() จะถูกดำเนินการ (การเชื่อมต่อที่อยู่ในคิวถูกใช้เพื่อเรียกสล็อต)
จากนั้น ฉันเชื่อมต่อสัญญาณ ChildProcesses::stopped() กับสล็อต ChildrenWatcher::stop() โดยใช้ Qt::DirectConnection เพราะฉันต้องดำเนินการแบบอะซิงโครนัส สิ่งนี้จำเป็นเพื่อให้เธรดของฉันหยุดเมื่อไม่จำเป็นต้องใช้ตัวจัดการ ChildProcesses อีกต่อไป วิธีการหยุด () มีลักษณะดังนี้:
void ChildrenWatcher::stop()
{
mutex.lock();
stopped = true;
mutex.unlock();
}
แล้ว ChildrenWatcher::watch():
void ChildrenWatcher::watch()
{
while (!isStopped()) {
// Blocking waitpid() call here.
// Maybe emit one of the three informational signals here too.
}
// Self-destruct now!
deleteLater();
}
โอ้ และเมธอด isStopped() เป็นเพียงวิธีที่สะดวกในการใช้ mutex ในเงื่อนไข while():
bool ChildrenWatcher::isStopped()
{
bool stopped;
mutex.lock();
stopped = this->stopped;
mutex.unlock();
return stopped;
}
สิ่งที่เกิดขึ้นที่นี่คือฉันตั้งค่าสถานะหยุดเมื่อฉันต้องการทำให้เสร็จ จากนั้นครั้งต่อไปที่ isStopped() จะถูกเรียกว่ามันจะคืนค่าเท็จและเธรดสิ้นสุด
แล้วจะเกิดอะไรขึ้นเมื่อ watch() วนซ้ำสิ้นสุดลง? มันเรียก DeleteLater() ดังนั้นวัตถุจะทำลายตัวเองทันทีที่การควบคุมถูกส่งกลับไปยังลูปเหตุการณ์ของเธรดซึ่งเกิดขึ้นทันทีหลังจากการเรียก DeleteLater() (เมื่อ watch() กลับมา) กลับไปที่ ChildProcesses::start() คุณจะเห็นว่ามีการเชื่อมต่อจากสัญญาณที่ถูกทำลาย() ของผู้เฝ้าดูไปยังช่อง exit() ของเธรด ซึ่งหมายความว่าเธรดจะเสร็จสิ้นโดยอัตโนมัติเมื่อผู้เฝ้าดูเสร็จสิ้น และเมื่อสร้างเสร็จแล้ว มันก็จะทำลายตัวเองด้วยเพราะสัญญาณที่เสร็จสิ้นแล้ว() ของมันเองนั้นเชื่อมต่อกับช่อง DeleteLater()
นี่เป็นแนวคิดเดียวกับที่ Maya โพสต์ แต่เนื่องจากฉันใช้สำนวนทำลายตัวเอง ฉันจึงไม่จำเป็นต้องขึ้นอยู่กับลำดับของการเรียกช่อง มันจะทำลายตัวเองก่อนเสมอ หยุดด้ายทีหลัง จากนั้นก็จะทำลายตัวเองด้วย ฉันสามารถกำหนดสัญญาณที่เสร็จสิ้นแล้ว() ในตัวผู้ปฏิบัติงาน จากนั้นเชื่อมต่อกับสัญญาณ DeleteLater() ของมันเอง แต่นั่นจะหมายถึงการเชื่อมต่อเพิ่มเติมเพียงครั้งเดียวเท่านั้น เนื่องจากฉันไม่ต้องการสัญญาณ Finish() เพื่อจุดประสงค์อื่น ฉันจึงเลือกที่จะเรียก DeleteLater() จากผู้ปฏิบัติงานเอง
Maya ยังกล่าวอีกว่าคุณไม่ควรจัดสรร QObjects ใหม่ในตัวสร้างของคนงานเพราะพวกมันจะไม่อยู่ในเธรดที่คุณย้ายคนงานไป ฉันจะบอกว่าทำมันต่อไปเพราะนั่นคือวิธีการทำงานของ OOP เพียงตรวจสอบให้แน่ใจว่า QObjects เหล่านั้นทั้งหมดเป็นลูกของผู้ปฏิบัติงาน (นั่นคือ ใช้ตัวสร้าง QObject(QObject*)) - moveToThread() ย้ายลูกทั้งหมดพร้อมกับวัตถุที่ถูกย้าย หากคุณต้องการมี QObjects ที่ไม่ใช่ลูกของอ็อบเจ็กต์ของคุณจริงๆ ให้แทนที่ MoveToThread() ในผู้ปฏิบัติงานของคุณเพื่อที่จะย้ายสิ่งที่จำเป็นทั้งหมดด้วย
person
Sergei Tachenov
schedule
16.08.2013