Solución:
Casi lo único que puedo pensar en agregar es afirmar además que QObject
s tienen afinidad con un solo hilo. Este suele ser el hilo que crea el QObject
. Entonces, si creas 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 subclasificar QThread
y creando tus objetos en el run()
método, manteniendo así sus cosas muy 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
s, conecta tus señales, crea tu QThread
, mueve tu QObjects
al QThread
e iniciar el hilo. Los mecanismos de señal / ranura garantizarán que los límites de los hilos se crucen de manera adecuada y segura.
Es posible que deba introducir la sincronización si tiene que llamar a métodos en su objeto fuera de ese mecanismo.
Sé que Qt tiene otras funciones de subprocesamiento agradables más allá de los subprocesos con las 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 el 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. Utilizo 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:
La clase ChildProcesses es un administrador de procesos secundarios que inicia nuevos procesos secundarios con llamadas a spawn (), mantiene la lista de los procesos que se están ejecutando actualmente, etc. Sin embargo, necesita realizar un seguimiento de los estados secundarios, lo que significa usar la llamada waitpid () en Linux o WaitForMultipleObjects en Windows. Solía llamar a estos en modo sin bloqueo usando un temporizador, pero ahora quiero una reacción más rápida, lo que significa modo de bloqueo. 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 (salido / señalado / Dios sabe lo que sucedió). Luego comienza la diversión principal.
Conecto QThread :: started () al método ChildrenWatcher :: watch () para que se inicie tan pronto como el hilo esté listo. Dado que el observador vive en el nuevo hilo, 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 :: stop () a la ranura ChildrenWatcher :: stop () usando Qt :: DirectConnection porque necesito hacerlo de forma asincrónica. Esto es necesario para que mi hilo se detenga cuando el administrador de ChildProcesses ya no sea necesario. 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 forma 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 detenido cuando necesito terminar, y luego, la próxima vez que se llama a isStopped (), devuelve falso y el hilo 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 eventos del hilo, lo que ocurre justo después de la llamada a deleteLater () (cuando vuelve watch ()). Volviendo a ChildProcesses :: start (), puede ver que hay una conexión desde la señal destrozada () del observador a la ranura quit () del hilo. Esto significa que el hilo finaliza automáticamente cuando termina el observador. Y cuando termina, también se autodestruye porque su propia señal de terminado () está conectada a su ranura deleteLater ().
Esta es más o menos la misma idea que publicó Maya, pero debido a que uso el idioma de autodestrucción, no necesito depender de la secuencia en la que se llaman las ranuras. Siempre se autodestruye primero, detiene el hilo más tarde y luego también se autodestruye. Podría definir una señal de terminado () en el trabajador y luego conectarlo a su propio deleteLater (), pero eso solo significaría una conexión más. Como no necesito una señal de terminado () para ningún otro propósito, elegí simplemente llamar a deleteLater () desde el 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 lo hagas de todos modos porque esa es la forma en que funciona OOP. Solo asegúrese de que todos esos QObjects sean hijos del trabajador (es decir, use el constructor QObject (QObject *)) – moveToThread () mueve todos los hijos junto con el objeto que se mueve. Si realmente necesita tener QObjects que no sean hijos de su objeto, anule moveToThread () en su trabajador para que mueva todas las cosas necesarias también.
Sin restar valor a 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 verificar el 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();
}