apa cara yang benar untuk mengimplementasikan QThread (mohon contohnya)

Dokumentasi Qt untuk QThread mengatakan untuk membuat kelas dari QThread, dan mengimplementasikan metode run.

Di bawah ini diambil dari dokumentasi 4.7 Qthread...

Untuk membuat thread Anda sendiri, subkelaskan QThread dan terapkan kembali run(). Misalnya:

 class MyThread : public QThread
 {
 public:
     void run();
 };

 void MyThread::run()
 {
     QTcpSocket socket;
     // connect QTcpSocket's signals somewhere meaningful
     ...
     socket.connectToHost(hostName, portNumber);
     exec();
 }

Jadi di setiap thread yang saya buat, saya telah melakukan hal itu dan untuk sebagian besar hal itu berfungsi dengan baik (saya tidak menerapkan moveToThread(this) di salah satu objek saya dan itu berfungsi dengan baik).

Saya mengalami hambatan minggu lalu (berhasil melewatinya dengan mengerjakan tempat saya membuat objek) dan menemukan mengikuti postingan blog. Di sini pada dasarnya dikatakan bahwa subkelas QThread sebenarnya bukan cara yang benar untuk melakukannya (dan dokumentasinya salah).

Ini berasal dari pengembang Qt jadi sekilas saya tertarik dan setelah direnungkan lebih lanjut, setuju dengannya. Mengikuti prinsip OO, Anda benar-benar hanya ingin membuat subkelas suatu kelas untuk lebih meningkatkan kelas itu... bukan hanya menggunakan metode kelas secara langsung... itulah mengapa Anda membuat instance...

Katakanlah saya ingin memindahkan kelas QObject khusus ke utas... apa cara yang 'benar' untuk melakukannya? Dalam postingan blog itu, dia 'mengatakan' dia punya contoh di suatu tempat... tetapi jika seseorang dapat menjelaskannya lebih lanjut kepada saya, itu akan sangat dihargai!

Pembaruan:

Karena pertanyaan ini mendapat banyak perhatian, berikut adalah salin dan tempel dokumentasi 4.8 dengan cara yang 'tepat' untuk mengimplementasikan QThread.

class Worker : public QObject
 {
     Q_OBJECT
     QThread workerThread;

 public slots:
     void doWork(const QString &parameter) {
         // ...
         emit resultReady(result);
     }

 signals:
     void resultReady(const QString &result);
 };

 class Controller : public QObject
 {
     Q_OBJECT
     QThread workerThread;
 public:
     Controller() {
         Worker *worker = new Worker;
         worker->moveToThread(&workerThread);
         connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
         connect(this, SIGNAL(operate(QString)), worker, SLOT(doWork(QString)));
         connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString)));
         workerThread.start();
     }
     ~Controller() {
         workerThread.quit();
         workerThread.wait();
     }
 public slots:
     void handleResults(const QString &);
 signals:
     void operate(const QString &);
 };

Saya masih percaya bahwa ada baiknya untuk menunjukkan bahwa mereka menyertakan anggota Worker::workerThread tambahan yang tidak diperlukan dan tidak pernah digunakan dalam contoh mereka. Hapus bagian itu dan ini adalah contoh yang tepat tentang cara melakukan threading di Qt.


person g19fanatic    schedule 04.11.2010    source sumber
comment
Dokumen hanya mengatakan tidak disarankan memperkenalkan slot baru di subkelas QThread. Tidak menyebutkan berasal dari kelas QThread. Berasal dari QThread mengikuti paradigma yang sama dengan TThread Delphi/C++ Builder.   -  person Zach Saw    schedule 26.11.2013
comment
woboq.com/blog/qthread-you-were- tidak-melakukan-yang-salah.html   -  person Zach Saw    schedule 26.11.2013
comment
Kode contohnya tidak akan dikompilasi sampai Anda mengubah baris 'connect' pertama menjadi alamat WorkThread, seperti: connect(&workerThread, SIGNAL(finished()),worker, SLOT(deleteLater() ));   -  person Vern Jensen    schedule 12.07.2014
comment
Saya pikir Anda harus menghapus QThreadworkerThread; di WorkerObject. Hal itu menimbulkan kesalahpahaman   -  person kien bui    schedule 07.05.2018


Jawaban (5)


Satu-satunya hal yang dapat saya pikirkan untuk ditambahkan adalah menyatakan lebih lanjut bahwa QObjects memiliki afinitas dengan satu utas. Biasanya ini adalah thread yang membuat QObject. Jadi jika Anda membuat QObject di thread utama aplikasi dan ingin menggunakannya di thread lain, Anda perlu menggunakan moveToThread() untuk mengubah afinitasnya.

Ini menghemat keharusan membuat subkelas QThread dan membuat objek Anda dalam metode run(), sehingga menjaga barang-barang Anda tetap terenkapsulasi dengan baik.

Entri blog tersebut menyertakan tautan ke contoh. Ini cukup singkat tetapi menunjukkan ide dasarnya. Buat QObject Anda, sambungkan sinyal Anda, buat QThread Anda, pindahkan QObjects Anda ke QThread dan mulai thread. Mekanisme sinyal/slot akan memastikan bahwa batas benang dilintasi dengan benar dan aman.

Anda mungkin harus memperkenalkan sinkronisasi jika Anda harus memanggil metode pada objek Anda di luar mekanisme itu.

Saya tahu Qt memiliki fasilitas threading yang bagus selain thread yang ada mungkin layak untuk diketahui tetapi saya belum melakukannya :)

person Arnold Spence    schedule 04.11.2010
comment
contoh tertaut juga mengatakan bahwa mereka melakukan subkelas QThread dan mengimplementasikan run() untuk melakukan exec(). Ini pada dasarnya akan memulai loop acara dan memungkinkan koneksi untuk melakukan tugasnya... Dari apa yang saya kumpulkan, Anda tidak perlu melakukan itu (dari posting asli yang saya cantumkan) atau apakah saya salah paham dan Anda masih perlu melakukannya ini? - person g19fanatic; 04.11.2010
comment
Anda mengerti dengan benar. Pada Qt 4.4, implementasi default run() melakukan ini untuk Anda. - person Arnold Spence; 04.11.2010

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
comment
Meskipun saya menghargai Anda menunjukkan penerapan manajer berbasis acara, itu tidak ada hubungannya dengan pertanyaan ini. Pertanyaannya adalah sehubungan dengan perbedaan dokumentasi antara bagaimana Qt dulu merekomendasikan penerapan thread dan cara yang 'tepat' untuk melakukannya (yang sekarang lebih baik dalam dokumentasi saat ini)... - person g19fanatic; 19.08.2013
comment
@ g19, saya menemukan pertanyaan Anda (dan beberapa halaman lainnya) saat mencari cara yang benar untuk menggunakan QThread di Google. Baru setelah itu saya menerapkan ini, dan kemudian saya menyadari bahwa itulah yang saya cari di Google. Jadi saya mempostingnya dengan harapan orang lain yang mencari cara yang benar menggunakan QThread di Google akan merasakan manfaatnya. - person Sergei Tachenov; 20.08.2013
comment
@ g19, oh, dan saya sedang bekerja dengan Qt 4.6 atau semacamnya, jadi saya tidak tahu bahwa mereka mengubah dokumennya. Namun dokumennya masih sangat terbatas dan tidak menjelaskan bagaimana melakukan apa yang saya perlukan (dan banyak orang lain yang memerlukannya), jadi saya rasa pertanyaannya masih valid. - person Sergei Tachenov; 20.08.2013
comment
Sekarang mereka semacam mempromosikan tanya-a-faq-pertanyaan-dan-jawab-sendiri untuk hal-hal semacam ini di SO, jadi Anda mungkin dapat mengubahnya menjadi pertanyaan terpisah (mencantumkan semua kendala yang Anda temukan secara detail), dan juga dapatkan beberapa suara positif yang bagus :) - person mlvljr; 27.10.2013
comment
Saya menemukan jawaban Anda sangat berguna, tetapi alangkah baiknya jika Anda memposting kode lengkap di suatu tempat (misalnya Intisari) untuk mendapatkan semua detailnya. - person parsley72; 27.04.2014
comment
@parsley, saya khawatir kode lengkapnya memiliki hak cipta. Dan tidak sendirian. - person Sergei Tachenov; 28.04.2014

Bukan untuk mengurangi jawaban bagus @ sergey-tachenov, tetapi di Qt5 Anda dapat berhenti menggunakan SIGNAL dan SLOT, menyederhanakan kode Anda dan mendapatkan keuntungan dari pemeriksaan waktu kompilasi:

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, ChildrenWatcher::exited,
            ChildProcesses::onChildExited);
    connect(childrenWatcher, ChildrenWatcher::signalled,
            ChildProcesses::onChildSignalled);
    connect(childrenWatcher, ChildrenWatcher::stateChanged,
            ChildProcesses::onChildStateChanged);
    // Make the watcher watch when the thread starts:
    connect(childrenWatcherThread, QThread::started,
            childrenWatcher, ChildrenWatcher::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, ChildProcesses::stopped,
            childrenWatcher, ChildrenWatcher::stop, Qt::DirectConnection);
    // Make the thread quit when the watcher self-destructs:
    connect(childrenWatcher, ChildrenWatcher::destroyed,
            childrenWatcherThread, QThread::quit);
    // Make the thread self-destruct when it finishes,
    // or rather, make the main thread delete it:
    connect(childrenWatcherThread, QThread::finished,
            childrenWatcherThread, QThread::deleteLater);
    childrenWatcherThread->start();
}
person Community    schedule 27.04.2014

mensubklasifikasikan kelas qthread akan tetap menjalankan kode di thread asal. Saya ingin menjalankan pendengar udp dalam aplikasi yang sudah menggunakan GUI Thread (thread utama) dan ketika pendengar udp saya bekerja dengan sempurna, GUI saya dibekukan karena diblokir oleh event handler qthread subkelas. Menurut saya apa yang diposting g19fanatic sudah benar, tetapi Anda juga memerlukan thread pekerja agar berhasil memigrasikan objek ke thread baru. Saya menemukan postingan ini yang menjelaskan secara detail Anjuran dan Larangan dalam threading di QT.

Harus Dibaca sebelum Anda memutuskan untuk membuat subkelas QThread!

person Community    schedule 18.08.2014
comment
Tidak benar. Kode yang berjalan di fungsi run() yang diganti akan berjalan di thread baru. - person Vincent; 04.05.2017
comment
Dari dokumen Qt: Penting untuk diingat bahwa instance QThread berada di thread lama yang membuat instance-nya, bukan di thread baru yang memanggil run(). Ini berarti bahwa semua slot antrian QThread akan dijalankan di thread lama. - person Vincent; 04.05.2017

Versi model thread praktik terbaik saya di Qt5 sesederhana ini: worker.h:

/*
* This is a simple, safe and best-practice way to demonstrate how to use threads in Qt5.
* Copyright (C) 2019 Iman Ahmadvand
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

#ifndef _WORKER_H
#define _WORKER_H

#include <QtCore/qobject.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qthread.h>
#include <QtGui/qevent.h>

namespace concurrent {

    class EventPrivate;
    class Event : public QEvent {
    public:
        enum {
            EventType1 = User + 1
        };

        explicit Event(QEvent::Type);
        Event(QEvent::Type, const QByteArray&);

        void setData(const QByteArray&);
        QByteArray data() const;

    protected:
        EventPrivate* d;
    };

    class WorkerPrivate;
    /* A worker class to manage one-call and permanent tasks using QThread object */
    class Worker : public QObject {
        Q_OBJECT

    public:
        Worker(QThread*);
        ~Worker();

    protected slots:
        virtual void init();

    protected:
        bool event(QEvent*) override;

    protected:
        WorkerPrivate* d;

    signals:
        /* this signals is used for one call type worker */
        void finished(bool success);
    };

} // namespace concurrent

#endif // !_WORKER_H

worker.cpp:

/*
* This is a simple, safe and best-practice way to demonstrate how to use threads in Qt5.
* Copyright (C) 2019 Iman Ahmadvand
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

#include "worker.h"

using namespace concurrent;

class concurrent::EventPrivate {
public:
    QByteArray data;
};

Event::Event(QEvent::Type t):QEvent(t), d(new EventPrivate) {
    setAccepted(false);
}

Event::Event(QEvent::Type t, const QByteArray& __data) : Event(t) {
    setData(__data);
}

void Event::setData(const QByteArray& __data) {
    d->data = __data;
}

QByteArray Event::data() const {
    return d->data;
}



class concurrent::WorkerPrivate {
public:
    WorkerPrivate() {

    }
};

Worker::Worker(QThread* __thread) :QObject(nullptr), d(new WorkerPrivate) {
    moveToThread(__thread);

    QObject::connect(__thread, &QThread::started, this, &Worker::init);
    QObject::connect(this, &Worker::finished, __thread, &QThread::quit);
    QObject::connect(__thread, &QThread::finished, __thread, &QThread::deleteLater);
    QObject::connect(__thread, &QThread::finished, this, &Worker::deleteLater);
}

Worker::~Worker() {
    /* do clean up if needed */
}

void Worker::init() {
    /* this will called once for construction and initializing purpose */
}

bool Worker::event(QEvent* e) {
    /* event handler */
    if (e->type() == Event::EventType1) {
        /* do some work with event's data and emit signals if needed */
        auto ev = static_cast<Event*>(e);
        ev->accept();
    }
    return QObject::event(e);
}

usage.cpp:

#include <QtCore/qcoreapplication.h>
#include "worker.h"

using namespace concurrent;

Worker* create(bool start) {
    auto worker = new Worker(new QThread);
    if (start)
        worker->thread()->start();

    return worker;
}

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);
    auto worker = create(true);
    if (worker->thread()->isRunning()) {
        auto ev = new Event(static_cast<QEvent::Type>(Event::EventType1));
        qApp->postEvent(worker, ev, Qt::HighEventPriority);
    }
    return app.exec();
}
person IMAN4K    schedule 14.10.2019