2010-11-04 30 views
54

La documentación de Qt para QThread dice crear una clase de QThread e implementar el método de ejecución.cuál es la forma correcta de implementar un QThread ... (ejemplo, por favor ...)

A continuación se toma de la documentación 4.7 QThread ...

Para crear sus propios hilos, subclase QThread y reimplementar 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 único que he creado, lo he hecho eso y para las cosas más funciona muy bien (yo no implementan moveToThread (este) en cualquiera de mis objetos y funciona genial).

Me encontré con un problema la semana pasada (logré superarlo trabajando alrededor de donde creé mis objetos) y encontré el following blog post. 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 mayor reflexión, 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 usar los métodos de clases directamente ... por eso creas ...

Digamos que quería cambiar una versión personalizada Clase QObject 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!

Actualización:

Dado que esta cuestión se pone tanta atención, aquí es una copia y pega de la documentación 4.8 con la forma 'adecuada' para 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 &); 
}; 

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

+1

Debe tenerse en cuenta que la nueva documentación en 4.8 establece la forma correcta de usar un QThread y desalienta activamente la antigua forma de Derivar de un objeto QThread solo para crear un objeto/subproceso de trabajo. http://qt-project.org/doc/qt-4.8/qthread.html#details – g19fanatic

+2

El doc solo dice que no se recomienda introducir nuevas ranuras en la subclase QThread. No mencionó derivar de la clase QThread. Derivado de QThread sigue el mismo paradigma de TThread Delphi/C++ Builder. –

+2

http://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html –

Respuesta

29

Lo único que se me ocurre agregar es afirmar que QObject s tienen 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 example. Es bastante corto pero muestra la idea básica. Cree su QObject s, conecte sus señales, cree su QThread, mueva su QObjects al QThread y comience 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 la sincronización si tiene que llamar a métodos en su objeto fuera de ese mecanismo.

Sé Qt tiene algún otro buen threading facilities allá de las discusiones que son probablemente vale la pena familiarizarse con, pero todavía tengo que hacerlo :)

+0

el ejemplo vinculado también dice que hacen la subclase QThread e implementan run() para hacer un exec(). Esto básicamente iniciará el ciclo de eventos y permitirá que las conexiones hagan lo suyo ... Por lo que veo, no debería necesitar hacer eso (desde la publicación original que enumeré) o estoy malentendido y aún tendrá que hacer ¿esta? – g19fanatic

+1

Usted comprende correctamente. A partir de Qt 4.4, la implementación predeterminada de run() hace esto por usted. –

8

Aquí es one example of how to use QThread correctly, pero tiene algunos problemas con ella, que se reflejan en el 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 programa, 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:

clase Los ChildProcesses es un gestor de procesos hijo que se inicia nuevos procesos hijo con la freza() llama, mantiene la lista de los procesos que se ejecutan en la actualidad y así sucesivamente. 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 bloqueo con un temporizador, pero ahora quiero una reacción más rápida, lo que significa modo de bloqueo. Ahí es donde el hilo entra en

La clase ChildrenWatcher se define como sigue:.

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() por lo que se inicia 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() tiene el siguiente aspecto:

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

Y luego ChildrenWatcher :: reloj():

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 sólo una manera conveniente utilizar un mutex el tiempo() condición:

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

Así que lo que sucede aquí es que me puse la bandera se detuvo cuando tengo que terminar, y entonces la próxima vez isStopped() se llama devuelve falso y termina el hilo.

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 como 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 el 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.

+0

mientras le agradezco que muestre su implementación del administrador basado en eventos, no es pertinente para esta pregunta. La pregunta fue en relación con la discrepancia de documentación entre cómo se implementó Qt para recomendar los hilos y la forma "correcta" de hacerlo (que ahora es mejor en la documentación actual) ... – g19fanatic

+1

@ g19, he encontrado su pregunta (y varias otras páginas) mientras busca en Google la forma correcta de usar QThread. Solo después de eso implementé esto, y luego me di cuenta de que era exactamente por lo que buscaba en Google. Así que lo publiqué con la esperanza de que alguien más que esté buscando la "forma correcta de usar QThread" lo encuentre útil. –

+0

@ g19, oh, y estaba trabajando con Qt 4.6 o algo así, así que no tenía idea de que habían cambiado los documentos.Pero los documentos aún son muy limitados y no explican cómo hacer lo que necesitaba (y muchas otras personas lo necesitan), así que supongo que la pregunta sigue siendo válida. –

3

no menoscabar excelente respuesta de @ Sergey-tachenov, pero en QT5 puede dejar de usar SEÑAL y la ranura, simplificar el código y tienen la ventaja de comprobación en tiempo de compilació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(); 
} 
2

de subclases de la clase QThread se todavía ejecuta 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é this 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 la subclase QTread!

+1

No es cierto. El código que se ejecuta en la función reemplazada run() se ejecutará en el nuevo hilo. – Vincent

+0

de documentos de Qt: es importante recordar que una instancia de QThread reside en el hilo anterior que la creó, no en el nuevo hilo que ejecuta run(). Esto significa que todas las ranuras en cola de QThread se ejecutarán en el hilo anterior. – Vincent

Cuestiones relacionadas