c++ - traves - metodo invoke c#
¿Qué sucede con la afinidad de subprocesos de un QObject creado en un subproceso de trabajo que luego finaliza? (6)
Digamos que llamo a QtConcurrent::run()
que ejecuta una función en un hilo de trabajo, y en esa función asigno dinámicamente varios QObjects (para uso posterior). Dado que se crearon en el hilo de trabajo, su afinidad de hilo debe ser la del hilo de trabajo. Sin embargo, una vez que finaliza el subproceso de trabajo, la afinidad de subprocesos de QObject ya no debe ser válida.
La pregunta: ¿Qt mueve automáticamente los QObjects al hilo principal, o somos responsables de moverlos a un hilo válido antes de que termine el hilo del trabajador?
¿Qt mueve automáticamente los QObjects al hilo principal o somos responsables de moverlos a un hilo válido antes de que termine el hilo del trabajador?
No , Qt no mueve QObject
automáticamente en el hilo padre.
Este comportamiento no está explícitamente documentado, así que hice una pequeña investigación del código fuente de Qt framework, rama principal.
QThread
inicia en QThreadPrivate::start
:
unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg)
{
...
thr->run();
finish(arg);
return 0;
}
void QThread::terminate()
{
Q_D(QThread);
QMutexLocker locker(&d->mutex);
if (!d->running)
return;
if (!d->terminationEnabled) {
d->terminatePending = true;
return;
}
TerminateThread(d->handle, 0);
d->terminated = true;
QThreadPrivate::finish(this, false);
}
En ambos casos, la finalización del hilo se realiza en QThreadPrivate::start :
void QThreadPrivate::finish(void *arg, bool lockAnyway)
{
QThread *thr = reinterpret_cast<QThread *>(arg);
QThreadPrivate *d = thr->d_func();
QMutexLocker locker(lockAnyway ? &d->mutex : 0);
d->isInFinish = true;
d->priority = QThread::InheritPriority;
bool terminated = d->terminated;
void **tls_data = reinterpret_cast<void **>(&d->data->tls);
locker.unlock();
if (terminated)
emit thr->terminated();
emit thr->finished();
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
QThreadStorageData::finish(tls_data);
locker.relock();
d->terminated = false;
QAbstractEventDispatcher *eventDispatcher = d->data->eventDispatcher;
if (eventDispatcher) {
d->data->eventDispatcher = 0;
locker.unlock();
eventDispatcher->closingDown();
delete eventDispatcher;
locker.relock();
}
d->running = false;
d->finished = true;
d->isInFinish = false;
if (!d->waiters) {
CloseHandle(d->handle);
d->handle = 0;
}
d->id = 0;
}
QEvent::DeferredDelete
evento QEvent::DeferredDelete
para la limpieza de QObject::deleteLater
, luego de limpiar los datos de TLS con QThreadStorageData::finish(tls_data)
y eventDispatcher
eliminados. Después de eso, QObject
no recibirá eventos de este hilo, pero la afinidad de subprocesos de QObject
mantiene igual. Es interesante ver la implementación de void QObject::moveToThread(QThread *targetThread)
para comprender cómo cambia la afinidad de subprocesos.
La implementación de void QThreadPrivate::finish(void *arg, bool lockAnyway)
deja en claro que QThread
no cambia la afinidad de subprocesos de QThread
.
Sin embargo, una vez que finaliza el subproceso de trabajo, la afinidad de subprocesos de QObject ya no debe ser válida.
El hilo de trabajo NO termina después de su llamada de función. El objetivo de utilizar QtConcurrent::run
es ejecutar un gran número de tareas pequeñas en el grupo de subprocesos global (o algunas proporcionadas QThreadPool
) mientras se reutilizan subprocesos para evitar la sobrecarga de crear y destruir subprocesos para cada una de estas pequeñas tareas. Además de distribuir el cómputo en todos los núcleos disponibles.
Puede intentar ver el código fuente de Qt para ver cómo se implementa QtConcurrent::run
. Verá que termina llamando a RunFunctionTaskBase::start
, que básicamente llama a QThreadPool::start
con un QRunnable
que llama a la función que se pasó inicialmente a QtConcurrent::run
.
Ahora el punto al que quiero llegar es que QThreadPool::start
se implemented agregando QRunnable
a una cola y luego tratando de QRunnable
uno de los subprocesos del grupo de subprocesos (que están waiting que se QRunnable
un nuevo QRunnable
agregado a la cola). Lo que hay que notar aquí, es que los subprocesos del grupo de subprocesos no están ejecutando un bucle de eventos (no están diseñados para actuar de esta manera), están ahí solo para ejecutar QRunnable
s en la cola y nada más (se implementan de esta manera por razones de rendimiento obviamente).
Esto significa que, en el momento en que está creando un objeto QObject
en una función ejecutada en QtConcurrent::run
, solo está creando un QObject
que vive en un hilo sin bucle de evento, de los docs , las restricciones incluyen:
Si no se está ejecutando ningún bucle de evento, los eventos no se entregarán al objeto. Por ejemplo, si crea un objeto
QTimer
en un hilo pero nunca llama aexec()
, elQTimer
nunca emitirá su señal detimeout()
. Llamar adeleteLater()
tampoco funcionará. (Estas restricciones se aplican también al hilo principal).
TL; DR: QtConcurrent::run
ejecuta funciones en subprocesos del QThreadPool
global (o uno proporcionado). Esos subprocesos no ejecutan un bucle de evento, simplemente esperan que se ejecute QRunnable
s. Entonces, un QObject
viva en un hilo de estos hilos no recibe ningún evento.
En la documentation , han puesto QThread
(posiblemente, con un bucle de evento y un objeto de trabajador) y el uso de QtConcurrent::run
como dos tecnologías separadas de multi-threading. No están destinados a mezclarse. Entonces, ningún trabajador objeta en grupos de hilos , esto es solo pedir problemas.
La pregunta: ¿Qt mueve automáticamente los QObjects al hilo principal, o somos responsables de moverlos a un hilo válido antes de que termine el hilo del trabajador?
Creo que después de ver las cosas de esta manera, la respuesta es obvia que Qt NO mueve el QObject
en ningún subproceso automáticamente. La documentación ha advertido sobre el uso de un QObject
en un QThread
sin un bucle de evento, y eso es todo.
Usted es libre de moverlos al hilo que desee. Pero tenga en cuenta que moveToThread()
veces puede causar problemas. Por ejemplo, si mover su objeto de trabajo implica mover un QTimer
:
Tenga en cuenta que todos los temporizadores activos para el objeto se restablecerán. Los temporizadores primero se detienen en el subproceso actual y se reinician (con el mismo intervalo) en el targetThread. Como resultado, mover constantemente un objeto entre hilos puede posponer eventos de temporizador indefinidamente.
Conclusión: creo que deberías considerar usar tu propio QThread
que ejecuta su ciclo de eventos, y crear tus QObject
trabajadores allí en lugar de utilizar QtConcurrent
. De esta forma es mucho mejor que mover QObject
s, y puede evitar muchos errores que pueden surgir del uso de su enfoque actual. Eche un vistazo a la documentation y elija la tecnología que mejor se adapte a su caso de uso. Solo use QtConcurrent
si solo desea ejecutar una función de llamada única y obtener su valor de retorno. Si desea una interacción permanente con el hilo, debe cambiar a usar su propio QThread
con QObject
s de trabajador.
A pesar de que los documentos de Qt no parecen especificar el comportamiento que se puede encontrar haciendo un seguimiento de lo que devuelve QObject::thread()
antes y después de que termine el hilo.
Aunque esta es una pregunta antigua, hace poco hice la misma pregunta y la respondí usando QT 4.8 y algunas pruebas.
AFAIK no puedes crear objetos con un padre de una función QtConcurrent :: run. He intentado las siguientes dos formas. Permítanme definir un bloque de código, luego exploraremos el comportamiento seleccionando POINTER_TO_THREAD.
Algún código de psuedo te mostrará mi prueba
Class MyClass : public QObject
{
Q_OBJECT
public:
doWork(void)
{
QObject* myObj = new QObject(POINTER_TO_THREAD);
....
}
}
void someEventHandler()
{
MyClass* anInstance = new MyClass(this);
QtConcurrent::run(&anInstance, &MyClass::doWork)
}
Ignorando posibles problemas de alcance ...
Si POINTER_TO_THREAD
está configurado a this
, recibirá un error porque se resolverá con un puntero al objeto anInstance
que vive en el hilo principal, no con el hilo que QtConcurrent ha enviado para él. Verás algo como ...
Cannot create children for a parent in another thread. Parent: anInstance, parents thread: QThread(xyz), currentThread(abc)
Si POINTER_TO_THREAD
está establecido en QObject::thread()
, entonces obtendrá un error porque se resolverá con el objeto QThread en el que vive anInstance
, y no con el hilo que QtConcurrent ha enviado para él. Verás algo como ...
Cannot create children for a parent in another thread. Parent: QThread(xyz), parents thread: QThread(xyz), currentThread(abc)
Espero que mi prueba sea útil para otra persona. Si alguien sabe cómo obtener un puntero al QThread en el que QtConcurrent ejecuta el método, ¡me interesaría escucharlo!
No estoy seguro si Qt cambia automáticamente la afinidad del hilo. Pero incluso si lo hace, el único hilo razonable para moverse es el hilo principal. Yo los empujaría al final de la función de enhebrado yo mismo.
myObject->moveToThread(QApplication::instance()->thread());
Ahora esto solo importa si los objetos hacen uso de un proceso de evento como las señales de envío y recepción.
QThread
no está documentado para mover automáticamente cualquier QObject
cuando termina, por lo que creo que ya podemos concluir que no existe tal cosa. Tal comportamiento sería muy sorprendente, y en desacuerdo con el resto de la API.
Para completar, probé con Qt 5.6:
QObject o;
{
QThread t;
o.moveToThread(&t);
for (int i = 0; i < 2; ++i)
{
t.start();
QVERIFY(t.isRunning());
QVERIFY(o.thread() == &t);
t.quit();
t.wait();
QVERIFY(t.isFinished());
QVERIFY(o.thread() == &t);
}
}
QVERIFY(o.thread() == nullptr);
Recuerde que un QThread
no es un hilo, sino que gestiona un hilo.
Cuando finaliza un QThread
, continúa existiendo y los objetos que viven en él continúan viviendo en él, pero ya no procesan eventos. El QThread
se puede reiniciar (no se recomienda), en cuyo punto se reanudará el procesamiento del evento (por lo que el mismo QThread
podría estar administrando un hilo diferente).
Cuando se destruye un QThread
, los objetos que vivían en él dejan de tener afinidad con el hilo. La documentation no garantiza esto, y de hecho dice "Debe asegurarse de que todos los objetos creados en un hilo se eliminen antes de eliminar el QThread
".
Digamos que llamo a
QtConcurrent::run()
que ejecuta una función en un hilo de trabajo, y en esa función asigno dinámicamente varios QObjects (para uso posterior). Dado que se crearon en el hilo de trabajo, su afinidad de hilo debe ser la del hilo de trabajo. Sin embargo, una vez que finaliza el subproceso de trabajo, la afinidad de subprocesos de QObject ya no debe ser válida.
El QThread
no termina en este escenario. Cuando finaliza una tarea generada por QtConcurrent::run
, el QThread
se estaba ejecutando se devuelve al QThreadPool
y puede reutilizarse mediante una llamada posterior a QtConcurrent::run
, y los QObject
viven en ese QThread
continúan viviendo allí.
QThreadPool::globalInstance()->setMaxThreadCount(1);
QObject *o = nullptr;
QThread *t = nullptr;
QFuture<void> f = QtConcurrent::run([&] {
o = new QObject;
t = o->thread();
QVERIFY(t == QThread::currentThread());
});
f.waitForFinished();
QVERIFY(t == o->thread());
QVERIFY(t->isRunning());
f = QtConcurrent::run([=] {
QVERIFY(t == QThread::currentThread());
});
f.waitForFinished();
Es posible que desee mover manualmente un objeto de un QThread
antes de que se devuelva a QThreadPool
, o simplemente no use QtConcurrent::run
. Tener una QtConcurrent::run
tarea construir QObject
s que sobrevivir la tarea es un diseño cuestionable, las tareas deben ser independientes. Como señala @Mike, los QThread
utilizados por QtConcurrent::run
no tienen bucles de eventos.