¿Cuál es la forma correcta de implementar un QThread … (ejemplo, por favor …)

La documentación de Qt para QThread dice crear una clase de QThread e implementar el método de ejecución.

A continuación se toma de la documentación 4.7 Qthread …

Para crear sus propios hilos, subclase QThread y reimplement run (). Por ejemplo:

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

Así que en cada hilo que he creado, he hecho exactamente eso y para la mayoría de las cosas funciona bien (no implemento moverToThread (esto) en ninguno de mis objetos y funciona muy bien).

Me tocó un inconveniente la semana pasada (lo superé trabajando en el lugar donde creé mis objetos) y encontré la siguiente publicación en el blog . Aquí básicamente se dice que la subclasificación de QThread realmente no es la forma correcta de hacerlo (y que la documentación es incorrecta).

Esto proviene de un desarrollador de Qt, por lo que a primera vista me interesó y después de una reflexión más profunda, estoy de acuerdo con él. Siguiendo los principios de OO, realmente solo quieres subclasificar una clase para mejorar aún más esa clase … no solo para usar los métodos de clases directamente … es por eso que creas …

Digamos que quería mover una clase de QObject personalizada a un hilo … ¿cuál sería la forma “correcta” de hacerlo? En esa publicación del blog, él “dice” que tiene un ejemplo en alguna parte … pero si alguien pudiera explicarme más, ¡sería muy apreciado!

Actualizar:

Dado que esta pregunta recibe tanta atención, aquí hay una copia y pega de la documentación 4.8 con la forma “correcta” de implementar un 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 &); }; 

Sigo creyendo que vale la pena señalar que incluyen un miembro Worker::workerThread adicional que es innecesario y nunca se usa en su ejemplo. Quite esa pieza y es un ejemplo apropiado de cómo enhebrar en Qt.

    Lo único que se me ocurre agregar es afirmar que QObject tiene afinidad con un solo hilo. Este suele ser el hilo que crea el QObject . Por lo tanto, si crea un QObject en el hilo principal de la aplicación y desea usarlo en otro hilo, debe usar moveToThread() para cambiar la afinidad.

    Esto ahorra tener que subclase QThread y crear sus objetos en el método run() , manteniendo sus cosas bien encapsuladas.

    Esa publicación de blog incluye un enlace a un ejemplo . Es bastante corto pero muestra la idea básica. Crea tu QObject , conecta tus señales, crea tu QThread , mueve tus QObjects al QThread y comienza el hilo. Los mecanismos de señal / ranura garantizarán que los límites del hilo se crucen de manera adecuada y segura.

    Puede que tenga que introducir sincronización si tiene que llamar a métodos en su objeto fuera de ese mecanismo.

    Sé que Qt tiene otras lindas herramientas para enhebrar más allá de los hilos que probablemente valga la pena familiarizarse, pero aún no lo he hecho 🙂

    Aquí hay un ejemplo de cómo usar QThread correctamente , pero tiene algunos problemas que se reflejan en los comentarios. En particular, dado que el orden en que se ejecutan las ranuras no está estrictamente definido, podría dar lugar a varios problemas. El comentario publicado el 6 de agosto de 2013 da una buena idea de cómo lidiar con este problema. Uso algo así en mi progtwig, y ​​aquí hay un código de ejemplo para aclarar.

    La idea básica es la misma: creo una instancia de QThread que vive en mi hilo principal, una instancia de clase trabajadora que vive en el nuevo hilo que creé, y luego conecto todas las señales.

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

    Algunos antecedentes:

    La clase ChildProcesses es un gestor de procesos hijo que inicia nuevos procesos secundarios con llamadas spawn (), mantiene la lista de los procesos actualmente en ejecución, etc. Sin embargo, necesita realizar un seguimiento de los estados secundarios, lo que significa usar waitpid () llamada en Linux o WaitForMultipleObjects en Windows. Solía ​​llamar a estos en modo sin locking con un temporizador, pero ahora quiero una reacción más rápida, lo que significa modo de locking. Ahí es donde entra el hilo.

    La clase ChildrenWatcher se define de la siguiente manera:

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

    Aquí cómo funciona. Cuando se inicia todo esto, se llama al método ChildProcess :: start () (ver arriba). Crea un nuevo QThread y un nuevo ChildrenWatcher, que luego se mueve al nuevo hilo. Luego conecto tres señales que informan a mi gerente sobre el destino de sus procesos secundarios (exit / signaled / god-knows-what-happened). Entonces comienza la diversión principal.

    Conecto QThread :: started () al método ChildrenWatcher :: watch () para que se inicie tan pronto como el hilo esté listo. Como el observador vive en el nuevo subproceso, ahí es donde se ejecuta el método watch () (la conexión en cola se usa para llamar a la ranura).

    Luego conecto la señal ChildProcesses :: stopped () a la ranura ChildrenWatcher :: stop () usando Qt :: DirectConnection porque necesito hacerlo de forma asíncrona. Esto es necesario para que mi hilo se detenga cuando ya no se necesite el administrador ChildProcesses. El método stop () se ve así:

     void ChildrenWatcher::stop() { mutex.lock(); stopped = true; mutex.unlock(); } 

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

    Ah, y el método isStopped () es solo una manera conveniente de usar un mutex en la condición while ():

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

    Entonces, lo que sucede aquí es que configuro la bandera detenida cuando necesito terminar, y luego la siguiente vez que se llama a IsStopped () devuelve falso y la cadena termina.

    Entonces, ¿qué sucede cuando termina el ciclo watch ()? Llama a deleteLater () para que el objeto se autodestruya tan pronto como se devuelva el control al bucle de evento de la secuencia que ocurre justo después de la llamada deleteLater () (cuando vuelve a aparecer watch ()). Volviendo a ChildProcesses :: start (), puede ver que hay una conexión desde la señal destroy () del vigilante hasta la ranura quit () del hilo. Esto significa que el hilo termina automáticamente cuando el observador está listo. Y cuando termina, se autodestruye también porque su propia señal de finalización () está conectada a su ranura deleteLater ().

    Esta es más o menos la misma idea que Maya publicó, pero debido a que utilizo el modismo de autodestrucción, no necesito depender de la secuencia en que se llaman las ranuras. Siempre se autodestruye primero, detiene el hilo más tarde, luego se autodestruye también. Podría definir una señal finished () en el worker, y luego conectarla a su propia deleteLater (), pero eso solo significaría una conexión más. Como no necesito una señal terminada () para ningún otro propósito, elijo simplemente llamar a deleteLater () del propio trabajador.

    Maya también menciona que no debe asignar nuevos QObjects en el constructor del trabajador porque no vivirán en el hilo al que mueve al trabajador. Yo diría que hacerlo de todos modos porque esa es la forma en que OOP funciona. Solo asegúrese de que todos esos QObjects sean hijos del trabajador (es decir, use el constructor QObject (QObject *)) – moveToThread () mueve todos los elementos secundarios junto con el objeto que se está moviendo. Si realmente necesita tener QObjects que no sean hijos de su objeto, entonces anule moveToThread () en su trabajador para que también mueva todo lo necesario.

    No menoscabar la excelente respuesta de @sergey-tachenov, pero en Qt5 puede dejar de usar SIGNAL y SLOT, simplificar su código y tener la ventaja de comprobar el tiempo de comstackción:

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

    la subclasificación de la clase qthread seguirá ejecutando el código en el hilo de origen. Quería ejecutar un oyente udp en la aplicación que ya está utilizando el subproceso GUI (el hilo principal) y mientras mi oyente udp funcionaba perfectamente, mi GUI estaba congelada, ya que estaba bloqueada por los manipuladores de eventos qthread subclase. Creo que lo que g19fanatic publicó es correcto, pero también necesitarás el hilo de trabajo para migrar con éxito el objeto al nuevo hilo. Encontré esta publicación que describe en detalles de lo que se debe y lo que no se debe hacer en QT.

    Debe leer antes de decidir subclase QThread!