Berikut salah satu contoh cara menggunakan QThread dengan benar, tetapi ada beberapa masalah, yang tercermin dalam komentar. Khususnya, karena urutan eksekusi slot tidak ditentukan secara ketat, hal ini dapat menimbulkan berbagai masalah. Komentar yang diposting pada 6 Agustus 2013 memberikan ide bagus bagaimana menangani masalah ini. Saya menggunakan sesuatu seperti itu dalam program saya, dan berikut beberapa contoh kode untuk memperjelas.
Ide dasarnya sama: Saya membuat instance QThread yang ada di thread utama saya, instance kelas pekerja yang ada di thread baru yang saya buat, dan kemudian saya menghubungkan semua sinyal.
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();
}
Beberapa latar belakang:
Kelas ChildProcesses adalah manajer proses anak yang memulai proses anak baru dengan panggilan spawn(), menyimpan daftar proses yang sedang berjalan, dan seterusnya. Namun, ia perlu melacak status anak-anak, yang berarti menggunakan panggilan waitpid() di Linux atau WaitForMultipleObjects di Windows. Saya biasa menyebutnya dalam mode non-pemblokiran menggunakan pengatur waktu, tetapi sekarang saya ingin reaksi yang lebih cepat, yang berarti mode pemblokiran. Di situlah thread masuk.
Kelas ChildrenWatcher didefinisikan sebagai berikut:
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);
};
Inilah cara kerjanya. Ketika semua hal ini dimulai, metode ChildProcess::start() dipanggil (lihat di atas). Ini menciptakan QThread baru dan ChildrenWatcher baru, yang kemudian dipindahkan ke thread baru. Kemudian saya menghubungkan tiga sinyal yang memberi tahu manajer saya tentang nasib proses turunannya (keluar/diberi sinyal/Tuhan-tahu-apa-yang-terjadi). Kemudian dimulailah kesenangan utama.
Saya menghubungkan QThread::started() ke metode ChildrenWatcher::watch() sehingga dimulai segera setelah thread siap. Karena pengamat tinggal di thread baru, di situlah metode watch() dijalankan (koneksi antrian digunakan untuk memanggil slot).
Kemudian saya menghubungkan sinyal ChildProcesses::stopped() ke slot ChildrenWatcher::stop() menggunakan Qt::DirectConnection karena saya perlu melakukannya secara asinkron. Ini diperlukan agar thread saya berhenti ketika manajer ChildProcesses tidak lagi diperlukan. Metode stop() terlihat seperti ini:
void ChildrenWatcher::stop()
{
mutex.lock();
stopped = true;
mutex.unlock();
}
Dan kemudian 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();
}
Oh, dan metode isStopped() hanyalah cara mudah untuk menggunakan mutex dalam kondisi while():
bool ChildrenWatcher::isStopped()
{
bool stopped;
mutex.lock();
stopped = this->stopped;
mutex.unlock();
return stopped;
}
Jadi apa yang terjadi di sini adalah saya menyetel tanda berhenti ketika saya harus menyelesaikannya, dan saat isStopped() dipanggil lagi, ia mengembalikan false dan thread berakhir.
Jadi apa yang terjadi ketika loop watch() berakhir? Ia memanggil deleteLater() sehingga objek akan hancur sendiri segera setelah kontrol dikembalikan ke loop peristiwa thread yang terjadi tepat setelah panggilan deleteLater() (saat watch() kembali). Kembali ke ChildProcesses::start(), Anda dapat melihat bahwa ada koneksi dari sinyal destroy() dari pengamat ke slot quit() pada thread. Ini berarti bahwa thread secara otomatis selesai ketika pengamat selesai. Dan ketika selesai, ia juga akan hancur sendiri karena sinyal finish()-nya terhubung ke slot deleteLater()-nya.
Ini hampir sama dengan ide yang diposting Maya, tetapi karena saya menggunakan idiom penghancuran diri, saya tidak perlu bergantung pada urutan pemanggilan slot. Itu selalu menghancurkan dirinya sendiri terlebih dahulu, menghentikan thread nanti, lalu menghancurkan dirinya sendiri juga. Saya dapat mendefinisikan sinyal selesai() pada pekerja, dan kemudian menghubungkannya ke deleteLater() miliknya sendiri, tetapi itu hanya berarti satu koneksi lagi. Karena saya tidak memerlukan sinyal finish() untuk tujuan lain, saya memilih untuk memanggil deleteLater() dari pekerja itu sendiri.
Maya juga menyebutkan bahwa Anda tidak boleh mengalokasikan QObjects baru di konstruktor pekerja karena mereka tidak akan tinggal di thread tempat Anda memindahkan pekerja. Saya akan mengatakan tetap melakukannya karena itulah cara kerja OOP. Pastikan saja semua QObject tersebut adalah anak dari pekerja (yaitu, gunakan konstruktor QObject(QObject*)) - moveToThread() memindahkan semua anak bersama dengan objek yang dipindahkan. Jika Anda benar-benar perlu memiliki QObjects yang bukan merupakan turunan dari objek Anda, timpa moveToThread() di pekerja Anda sehingga ia juga memindahkan semua hal yang diperlukan.
person
Sergei Tachenov
schedule
16.08.2013