как правильно реализовать QThread... (пример пожалуйста…)


в документации Qt для QThread говорится о создании класса из QThread и реализации метода run.

ниже взято из документации 4.7 Qthread...

для создания собственных потоков, подкласс QThread и переопределить метод run(). Например:

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

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

Так что в каждом потоке, который я создал, я сделал именно это, и для большинства вещей он работает просто отлично (я не реализую moveToThread (это) в любом из моих объектов и это работает отлично).

я попал в ловушку на прошлой неделе (удалось пройти через него, работая вокруг того, где я создал свои объекты) и нашел блоге. Здесь в основном говорит, что подклассы QThread не правильный способ сделать это (и что в документации неправильно).

это исходит от разработчика Qt, поэтому на первый взгляд мне было интересно и после дальнейшего размышления, согласитесь с ним. Следуя принципам OO, вы действительно хотите только подкласс класса для дальнейшего расширения этого класса... не просто использовать методы классов напрямую... вот почему вы создаете экземпляр...

допустим, я хотел переместить пользовательский класс QObject в поток... каков был бы "правильный" способ сделать это? В этом блоге, он говорит, что у него есть где-то пример... но если бы кто-то мог объяснить мне это, я был бы очень признателен!

обновление:

Так как этот вопрос получает так много внимания, здесь является копией и вставкой документации 4.8 с "правильным" способом реализации 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 &);
 };

Я все еще считаю, что стоит отметить, что они включают в себя дополнительный Worker::workerThread элемент, который является ненужным и никогда не используется в их примере. Удалите эту часть, и это правильный пример того, как делать резьбу в Qt.

4 58

4 ответа:

о единственное, что я могу придумать, чтобы добавить, это еще раз заявить, что QObjects имеют сродство с одной нитью. Обычно это поток, который создает QObject. Так что если вы создадите QObject в главном потоке приложения и хотите использовать его в другом потоке, вам нужно использовать moveToThread() изменить сродство.

это избавляет от необходимости подкласса QThread и создание ваших объектов в run() метод, таким образом, сохраняя ваши вещи хорошо инкапсулированы.

что сообщение в блоге содержит ссылку на пример. Это довольно короткий, но он показывает основную идею. Создайте свой QObjects, подключите свои сигналы, создайте свой QThread, перемещение QObjects до QThread и запустить поток. Сигнальные / щелевые механизмы обеспечат правильное и безопасное пересечение границ резьбы.

возможно, вам придется ввести синхронизацию, если вам нужно вызвать методы на вашем объекте вне этого механизма.

Я знаю, что Qt имеет некоторые другие хорошие продевая нитку средства за пределами темы, которые, вероятно, стоит ознакомиться, но я еще не сделал этого :)

здесь один пример того, как правильно использовать 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 on Окна. Раньше я вызывал их в неблокирующем режиме с помощью таймера, но теперь мне нужна более быстрая реакция, что означает режим блокировки. Вот где нить входит.

класс 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 больше не нужен. Метод stop () выглядит следующим образом:

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 () - это просто удобный способ использовать мьютекс в состоянии while ():

bool ChildrenWatcher::isStopped()
{
    bool stopped;
    mutex.lock();
    stopped = this->stopped;
    mutex.unlock();
    return stopped;
}

так что здесь происходит то, что я остановился флаг, когда мне нужно закончить, и тогда в следующий раз isStopped () называется он возвращает false и поток заканчивается.

так что же происходит, когда цикл watch() заканчивается? Он вызывает deleteLater (), поэтому объект самоуничтожается, как только управление возвращается в цикл событий потока, который происходит сразу после вызова deleteLater () (когда возвращается watch ()). Возвращаясь к ChildProcesses:: start(), вы можете видеть, что существует соединение от разрушенного() сигнала наблюдателя до слота quit() потока. Это означает, что поток автоматически заканчивается, когда наблюдатель будет сделано. И когда он закончен, он также самоуничтожается, потому что его собственный сигнал finished() подключен к его слоту deleteLater ().

это в значительной степени та же идея, что и Майя, но поскольку я использую идиому самоуничтожения, мне не нужно зависеть от последовательности, в которой вызываются слоты. Это всегда самоуничтожение сначала, остановить поток позже, то он самоуничтожается тоже. Я мог бы определить готовый () сигнал в работнике, а затем подключить его к своему собственному deleteLater (), но это будет означать только одно соединение больше. Поскольку мне не нужен готовый сигнал () для любой другой цели, я решил просто вызвать deleteLater () из самого рабочего.

Майя также упоминает, что вы не должны выделять новые QObjects в конструкторе рабочего, потому что они не будут жить в потоке, в который вы перемещаете рабочий. Я бы сказал, сделать это в любом случае потому что ООП работает. Просто убедитесь, что все эти QObjects являются дочерними объектами работника (то есть используйте QObject (QObject*) constructor) - moveToThread () перемещает все дочерние элементы вместе с перемещаемым объектом. Если вам действительно нужно иметь QObjects, которые не являются дочерними объектами вашего объекта, то переопределите moveToThread() в своем рабочем устройстве, чтобы он также перемещал все необходимые вещи.

чтобы не отвлекаться от отличного ответа @sergey-tachenov, но в Qt5 вы можете прекратить использовать сигнал и слот, упростить свой код и иметь преимущество проверки времени компиляции:

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();
}

подкласс класса qthread по-прежнему будет выполнять код в исходном потоке. Я хотел запустить прослушиватель udp в приложении, которое уже использует поток GUI(основной поток), и пока мой прослушиватель udp работал отлично, мой графический интерфейс был заморожен, поскольку он был заблокирован обработчиками событий QThread подкласса. Я думаю, что g19fanatic posted является правильным, но вам также понадобится рабочий поток для успешной миграции объекта в новый поток. Я нашел этой пост, который подробно описывает Do и Dont потоков в QT.

должен прочитать, прежде чем вы решите подкласс QThread !