Qt](/proyecto-qt-framework-de-desarrollo-de-aplicaciones/) proporciona clases para hilos y mecanismos de sincronización que facilitan sacar las tareas de larga duración del hilo principal de la aplicación, lo que de lo contrario bloquearía la interfaz de usuario.
Una forma práctica de hacerlo la hemos visto anteriormente utilizando un buffer compartido. Sin embargo, Qt provee a cada hilo de una cola de mensajes, lo que permite enviar señales a slots en otros hilos. Esto nos proporciona una forma sencilla de pasar datos entre los hilos de la aplicación.
Si no se indica lo contrario, las señales emitidas desde un hilo a un objeto en el mismo hilo son entregadas directamente. Es decir, que al emitir la señal se invoca el slot como si de un método convencional se tratara. Sin embargo, si el emisor y el receptor residen en hilos diferentes, la señal es insertada en la cola de mensajes del hilo del objeto de destino. Así el slot correspondiente será invocado en el hilo receptor desde su bucle de mensajes.
En la actualidad esta es la forma recomendada de usar hilos en Qt, ya que permite evitar el uso de mecanismos de sincronización como QMutex, QWaitCondition, etc.
El ejemplo. Ordenar números enteros
El ejemplo que vamos a seguir básicamente consiste en ordenar un vector de enteros en un hilo de trabajo distinto al hilo principal.
Como se puede observar en la figura utilizaremos dos objetos, uno vinculado al hilo principal (clase Sorter
) y otro al hilo de trabajo (clase SorterWorker
). En una aplicación gráfica convencional con Qt la clase Sorter
podría ser una ventana o cualquier otro control que quiera ceder una tarea al hilo de trabajo. Aquí no lo haremos así para que el ejemplo sea lo más sencillo posible.
En Qt un objeto se dice que vive en el hilo en el que es creado. Esto se puede cambiar utilizando el método moveToThread() que tienen todas las clases de Qt que heredan de la clase base QObject.
La clase Sorter
class Sorter : public QObject
{
Q_OBJECT
public:
Sorter();
~Sorter();
// Ordenar asíncronamente un vector en el hilo de trabajo
void sortAsync(const QVector<int>& list);
signals:
// Señal para comunicarnos con el hilo de trabajo
void sortingRequested(const QVector<int>& list);
private slots:
// Slot para saber cuando el vector ha sido ordenado
void vectorSorted(const QVector<int> &list);
private:
// Clase del hilo de trabajo
QThread workingThread_;
// Clase que hace el ordenamiento
SorterWorker sorterWorker_;
};
La propia clase Sorter
se hará cargo de crear el hilo de trabajo, que por defecto lo único que hace es iterar en su propio bucle de mensajes. Todos los detalles acerca de la creación de hilos ya los vimos anteriormente
La clase Sorter
provee un método sortAsync()
que podrá ser llamado por los clientes para ordenar un vector de números enteros. Puesto que la operación es asíncrona, necesitamos definir un slot vectorSorted()
para ser notificados cuando el ordenamiento haya finalizado con éxito.
La implementación de esta clase sería la siguiente:
Sorter::Sorter() : QObject()
{
qDebug() << Q_FUNC_INFO << QThread::currentThreadId();
// Registrar los parámetros de la señales. Necesitamos registrar
// QList<int> porque no es un tipo conocido por el sistema de
// meta-objetos de Qt.
qRegisterMetaType< QVector<int> >("QVector<int>");
// Pasar la petición de ordenar a la instancia de SorterWorker
connect(this, SIGNAL(sortingRequested(QVector<int>)),
&sorterWorker_, SLOT(doSort(QVector<int>)));
// Ser notificado cuando el vector haya sido ordenado
connect(&sorterWorker_, SIGNAL(vectorSorted(QVector<int>)),
this, SLOT(vectorSorted(QVector<int>)));
// Migrar la instancia de SorterWorker al hilo de trabajo
sorterWorker_.moveToThread(&workingThread_);
// Iniciar el hilo de trabajo
workingThread_.start();
}
Sorter::~Sorter()
{
// Le decimos al bucle de mensajes del hilo que se detenga
workingThread_.quit();
// Ahora esperamos a que el hilo de trabajo termine
workingThread_.wait();
}
void Sorter::sortAsync(const QVector<int>& list)
{
qDebug() << Q_FUNC_INFO << QThread::currentThreadId();
emit sortingRequested(list);
}
void Sorter::vectorSorted(const QVector<int>& list)
{
qDebug() << list;
}
Como se puede observar, en el constructor de Sorter
se usa el método qRegisterMetaType()
, antes de conectar las señales, para registrar el tipo QVector<int>
. Esto debe hacerse porque cuando una señal es encolada, sus parámetros deben ser de tipos conocidos para Qt, de forma que pueda almacenar los argumentos en la cola.
Por otro lado, en el destructor de Sorter
tenemos cuidado de detener el hilo de trabajo en condiciones seguras cuando ya no va a ser necesario. Si no lo hacemos así, el hilo podría ser destruido por el sistema operativo en cualquier punto de la secuencia de instrucciones al terminar la aplicación, lo que podría dejar los datos en uso en un estado indeterminado.
La clase SorterWorker
class SorterWorker : public QObject
{
Q_OBJECT
signals:
// Señal emitida cuando el vector ha sido ordenado
void vectorSorted(const QVector<int> &list);
public slots:
// Método encargado del ordenamiento
void doSort(const QVector<int> &list) {
qDebug() << Q_FUNC_INFO << QThread::currentThreadId();
QVector<int> list_sorted = list;
qSort(list_sorted);
emit vectorSorted(list_sorted);
}
};
Como usar el ejemplo
Para usar el ejemplo solo necesitamos crear una instancia de Sorter
y llamar a su método sortAsync()
para pedir que ordene el vector especificado.
int main(int argc, char* argv[])
{
QCoreApplication a(argc, argv);
Sorter sorter;
sorter.sortAsync(QVector<int>() << 1 << 3 << 2);
return a.exec();
}
Referencias
Worker Thread in Qt using Signals & Slots — Chris' Blog [Archivado]