Esta sección explora el mecanismo de moveToThread en Qt, que permite desplazar un objeto QObject a un hilo diferente. Analizaremos los métodos comunes de hilos en Qt, seguido de un estudio del código fuente relacionado.
Métodos básicos para usar hilos en Qt
En Qt, hay varias formas de trabajar con hilos. Primero, se puede heredar de QThread y sobreescribir la función run.
/* mihilo.h */
#pragma once
#include <QThread>
class MiHilo : public QThread {
Q_OBJECT
public:
explicit MiHilo(QObject* padre = nullptr);
~MiHilo();
protected:
void run() override;
};
/* mihilo.cpp */
#include "mihilo.h"
#include <QDebug>
MiHilo::MiHilo(QObject* padre) : QThread(padre) {}
MiHilo::~MiHilo() {}
void MiHilo::run() {
for (int contador = 0; contador < 5; ++contador) {
qDebug() << u8"Ejecutando en hilo secundario";
QThread::msleep(500);
}
}
// Uso
auto* hilo = new MiHilo();
hilo->start();
Sin embargo, Qt recomienda mover objetos a hilos mediante moveToThread.
/* mitarea.h */
#pragma once
#include <QObject>
class MiTarea : public QObject {
Q_OBJECT
public:
MiTarea(QObject *padre = nullptr);
~MiTarea();
public slots:
void ejecutarTarea();
};
/* mitarea.cpp */
#include "mitarea.h"
#include <QThread>
#include <QDebug>
MiTarea::MiTarea(QObject *padre) : QObject(padre) {}
MiTarea::~MiTarea() {}
void MiTarea::ejecutarTarea() {
for (int iteracion = 0; iteracion < 5; ++iteracion) {
qDebug() << "Hilo actual:" << QThread::currentThread();
QThread::sleep(1);
}
}
// Uso
auto* tarea = new MiTarea();
auto* hilo = new QThread();
tarea->moveToThread(hilo);
connect(hilo, &QThread::started, tarea, &MiTarea::ejecutarTarea);
hilo->start();
Nota importante: un objeto con un padre no puede moverse a otro hilo. Si se intenta, Qt mostrará un error como "Cannot move objects with a parent".
Otra opción es usar QtConcurrent, aunque tiene limitaciones como la falta de control manual de finalización.
#include <QtConcurrent/QtConcurrent>
int tareaConcurrente(int valor) {
for (int i = 0; i < 3; ++i) {
qDebug() << "Valor:" << valor;
QThread::sleep(1);
}
return 0;
}
// Ejecución
QtConcurrent::run(this, &MainWindow::tareaConcurrente, 42);
Recuerde que en Qt, las actualizaciones de interfaz gráfica deben realizarse únicamente en el hilo principle.
Aálisis del código fuente de moveToThread
La función moveToThread realiza varias verificaciones antes de mover un objeto:
- Comprueba si el objeto ya está en el hilo destino.
- Verifica que el objeto no tenga un padre, ya que esto impide el movimiento.
- Los widgets no pueden moverse a otros hilos, ya que las operaciones de GUI deben ocurrir en el hilo principal.
// Ejemplo simplificado de validación
if (d->threadData.loadRelaxed()->thread.loadAcquire() == hiloDestino) {
return; // Ya está en el hilo
}
if (d->parent != nullptr) {
qWarning("moveToThread: Objeto con padre no puede moverse.");
return;
}
if (d->isWidget) {
qWarning("moveToThread: Widgets no pueden moverse.");
return;
}
Luego, se verifica la pertenencia al hilo actual. Si el objeto no tiene afinidad de hilo, puede moverse al hilo destino solo si la operación se realiza en el hilo destino. De lo contrario, si el hilo actual no coincide con el hilo del objeto, la operación no está permitida.
// Verificación de hilos
QThreadData *datosActuales = QThreadData::current();
QThreadData *datosDestino = hiloDestino ? QThreadData::get2(hiloDestino) : nullptr;
QThreadData *datosObjeto = d->threadData.loadRelaxed();
if (!datosObjeto->thread.loadAcquire() && datosActuales == datosDestino) {
datosActuales = datosObjeto;
} else if (datosObjeto != datosActuales) {
qWarning("moveToThread: Hilo actual no es el del objeto.");
return;
}
Finalmente, se realiza el movimiento bloqueando los eventos y actualizando los datos del hilo para el objeto.
// Operación de movimiento
d->moveToThread_helper();
if (!datosDestino) datosDestino = new QThreadData(0);
QMutexLocker bloqueo(signalSlotLock(this));
QOrderedMutexLocker locker(&datosActuales->postEventList.mutex, &datosDestino->postEventList.mutex);
datosActuales->ref();
d_func()->setThreadData_helper(datosActuales, datosDestino);
locker.unlock();
datosActuales->deref();
Consejos sobre hilos y señales en Qt
- Actualizaciones de UI deben hacerse en el hilo principal; use señales para notificar desde hilos secundarios.
- Para conexiones entre hilos, prfeiera
QueuedConnection, que ejecuta la ranura en el hilo del receptor. - Registre tipos personalizados con
qRegisterMetaTypeal usarQueuedConnection. - Evite
BlockQueuedConnectionsi los emisor y receptor están en el mismo hilo para prevenir bloqueos.