Qt
Roscado y concurrencia
Buscar..
Observaciones
Algunas notas que ya se mencionan en los documentos oficiales aquí y aquí :
- Si un objeto tiene un padre, tiene que estar en el mismo hilo que el padre, es decir, no se puede mover a un hilo nuevo, ni se puede establecer un padre a un objeto si el padre y el objeto viven en hilos diferentes.
- Cuando un objeto se mueve a un nuevo hilo, todos sus hijos también se mueven al nuevo hilo
- Solo puedes empujar objetos a un nuevo hilo. No puede arrastrarlos a un nuevo hilo, es decir, solo puede llamar a
moveToThread
desde el hilo en el que el objeto vive actualmente.
Uso básico de QThread
QThread
es un identificador de un hilo de plataforma. Le permite administrar el hilo monitoreando su vida útil y solicitando que finalice su trabajo.
En la mayoría de los casos no se recomienda heredar de la clase. El método de run
predeterminado inicia un bucle de eventos que puede enviar eventos a objetos que viven en la clase. Las conexiones de ranura de señal entre hilos se implementan mediante el envío de un QMetaCallEvent
al objeto de destino.
Una instancia de QObject
se puede mover a un subproceso, donde procesará sus eventos, como eventos de temporizador o llamadas de ranura / método.
Para trabajar en un hilo, primero cree su propia clase de trabajador que se deriva de QObject
. Luego muévelo al hilo. El objeto puede ejecutar su propio código automáticamente, por ejemplo, utilizando QMetaObject::invokeMethod()
.
#include <QObject>
class MyWorker : public QObject
{
Q_OBJECT
public:
Q_SLOT void doWork() {
qDebug() << "doWork()" << QThread::currentThread();
// and do some long operation here
}
MyWorker(QObject * parent = nullptr) : QObject{parent} {}
};
class MyController : public QObject
{
Q_OBJECT
Worker worker;
QThread workerThread;
public:
MyController() {
worker.moveToThread(&workerThread);
// provide meaningful debug output
workerThread.setObjectName("workerThread");
workerThread.start();
// the thread starts the event loop and blocks waiting for events
}
~MyController() {
workerThread.quit();
workerThread.wait();
}
void operate() {
// Qt::QueuedConnection ensures that the slot is invoked in its own thread
QMetaObject::invokeMethod(&worker, "doWork", Qt::QueuedConnection);
}
};
Si su trabajador debería ser efímero y solo existir mientras se realiza su trabajo, es mejor enviar un functor o un método seguro para subprocesos para su ejecución en el grupo de subprocesos a través de QtConcurrent::run
.
QtConcurrent Run
Si considera que la gestión de QThreads y primitivas de bajo nivel como mutex o semáforos es demasiado compleja, lo que está buscando es el espacio de nombres concurrente de Qt. Incluye clases que permiten una mayor gestión de hilos de alto nivel.
Echemos un vistazo a la carrera concurrente. QtConcurrent::run()
permite ejecutar la función en un nuevo hilo. ¿Cuándo te gustaría usarlo? Cuando tienes alguna operación larga y no quieres crear un hilo manualmente.
Ahora el código:
#include <qtconcurrentrun.h>
void longOperationFunction(string parameter)
{
// we are already in another thread
// long stuff here
}
void mainThreadFunction()
{
QFuture<void> f = run(longOperationFunction, "argToPass");
f.waitForFinished();
}
Así que las cosas son simples: cuando necesitamos ejecutar otra función en otro subproceso, simplemente llame a QtConcurrent::run
, pass function y sus parámetros, ¡y eso es todo!
QFuture
presenta el resultado de nuestro cálculo asíncrono. En el caso de QtConcurrent::run
no podemos cancelar la ejecución de la función.
Invocando ranuras de otros hilos
Cuando se usa un bucle de eventos Qt para realizar operaciones y un usuario que no es de Qt-saavy necesita interactuar con ese bucle de eventos, escribir la ranura para manejar invocaciones regulares de otro hilo puede simplificar las cosas para otros usuarios.
main.cpp:
#include "OperationExecutioner.h"
#include <QCoreApplication>
#include <QThread>
int main(int argc, char** argv)
{
QCoreApplication app(argc, argv);
QThread thrd;
thrd.setObjectName("thrd");
thrd.start();
while(!thrd.isRunning())
QThread::msleep(10);
OperationExecutioner* oe = new OperationExecutioner;
oe->moveToThread(&thrd);
oe->doIt1(123,'A');
oe->deleteLater();
thrd.quit();
while(!thrd.isFinished())
QThread::msleep(10);
return 0;
}
OperationExecutioner.h:
#ifndef OPERATION_EXECUTIONER_H
#define OPERATION_EXECUTIONER_H
#include <QObject>
class OperationExecutioner : public QObject
{
Q_OBJECT
public slots:
void doIt1(int argi, char argc);
};
#endif // OPERATION_EXECUTIONER_H
OperationExecutioner.cpp:
#include "OperationExecutioner.h"
#include <QMetaObject>
#include <QThread>
#include <QDebug>
void OperationExecutioner::doIt1(int argi, char argc)
{
if (QThread::currentThread() != thread()) {
qInfo() << "Called from thread" << QThread::currentThread();
QMetaObject::invokeMethod(this, "doIt1", Qt::QueuedConnection,
Q_ARG(int,argi), Q_ARG(char,argc));
return;
}
qInfo() << "Called from thread" << QThread::currentThread()
<< "with args" << argi << argc;
}
OperationExecutioner.pro:
HEADERS += OperationExecutioner.h
SOURCES += main.cpp OperationExecutioner.cpp
QT -= gui