Как правильно реализовать QThread ... (пример, пожалуйста ...) - PullRequest
61 голосов
/ 04 ноября 2010

В документации 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 (this) в любомиз моих объектов, и это прекрасно работает).

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

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

Допустим, я хотел переехатьпользовательский класс 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 ]

31 голосов
/ 04 ноября 2010

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

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

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

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

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

9 голосов
/ 17 августа 2013

Вот один пример того, как правильно использовать 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 :: start () к методу ChildrenWatcher :: watch (), чтобы он запускался, как только поток готов.Поскольку наблюдатель живет в новом потоке, именно здесь выполняется метод watch () (подключение к очереди используется для вызова слота).

Затем я подключаю сигнал ChildProcesses :: stop () к 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();
}

Oh, а метод isStopped () - это просто удобный способиспользуйте мьютекс в условии while ():

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

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

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

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

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

3 голосов
/ 27 апреля 2014

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

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();
}
2 голосов
/ 19 августа 2014

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

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

...