Me he enfrentado a un problema bastante extraño con QtConcurrent, principalmente debido a extraños deseos de programación, tal vez es solo un problema XY, pero ...

Entonces, ahí está mi código, tratando de comunicarme con la base de datos, un código de fondo en realidad (en Qt, sí). Tiene que funcionar rápido y manejar algunas solicitudes, por lo que necesito un grupo de subprocesos. Como hecho bien conocido, supongo que la conexión que se establece a sí misma es una operación que consume mucho tiempo, por lo que es necesario que las conexiones de bases de datos persistentes den como resultado hilos persistentes (QSqlDatabase no se puede mover entre los hilos). También es bastante natural querer el manejo de solicitudes asíncronas, lo que resulta en la necesidad de una forma simple de pasarlas a los hilos persistentes.

Nada demasiado complejo, supongamos que ya existe algún repetitivo en una forma como ...


// That's what I want for now
QFuture<int> res = workers[i]->async(param1, param2);

// OR

// That's what I DO NOT want to get
workers[i]->async(param1, param2, [](QFuture<int> res) { // QFuture to pass exceptions
     // callback here
});

Eso se puede hacer con seguridad. ¿Por qué no std::future? Bueno, es mucho más fácil usar QFutureWatcher y son señales de notificaciones sobre la preparación del resultado. Las soluciones de notificación de C ++ puras son muuuch más complejas y las devoluciones de llamada también son algo que debe arrastrarse a través de la jerarquía de clases. Cada trabajador interconecta un hilo con conexiones DB, obviamente.

De acuerdo, todo eso se puede escribir, pero ... el grupo de subprocesos personalizado significaría que no hay QtConcurrent conveniencia, parece que solo hay formas arriesgadas de crear ese QFuture para que pueda ser devuelto por la costumbre trabajador. QThreadPool no sirve de nada, ya que sería una gran historia para crear ejecutables persistentes en él. Es más, la placa repetitiva que describí brevemente será algún tipo de núcleo del proyecto, utilizado en muchos lugares, no algo que pueda ser reemplazado fácilmente por un manejo de 100 hilos hechos a mano.

En resumen: si pudiera construir un QFuture para mis resultados, el problema se resolvería. ¿Alguien podría señalarme una solución o una solución? Agradecería cualquier idea brillante.


UPD:

@VladimirBershov ofreció una buena solución moderna que implementa el patrón de observación. Después de buscar en Google, encontré una biblioteca QPromise. Por supuesto, la construcción de un QFuture personalizado todavía es hacky y solo se puede hacer a través de la clase QFutureInterface indocumentada, pero aún así, alguna solución "similar a una promesa" hace que las llamadas asincrónicas sean más claras hasta donde puedo juzgar.

1
MasterAler 17 feb. 2020 a las 21:17

2 respuestas

La mejor respuesta

Puede usar AsyncFuture como una herramienta de creación personalizada QFuture o fuente de ideas:

AsyncFuture: use QFuture como un objeto Promise

QFuture se usa junto con QtConcurrent para representar el resultado de un cálculo asincrónico. Es un componente poderoso para la programación multihilo. Pero su uso está limitado al resultado de hilos, no funciona con la señal asincrónica emitida por QObject. Y es un poco problemático configurar la función de escucha a través de QFutureWatcher.

AsyncFuture está diseñado para mejorar la función y ofrecer una mejor manera de usarla para la programación asincrónica. Proporciona un objeto Promise como interfaz. Este proyecto está inspirado en AsynQt y RxCpp.

Características:

  • Convierta una señal de QObject en un objeto QFuture
  • Combina múltiples futuros con diferentes tipos en un solo objeto futuro
  • Usa el futuro como un objeto Promise
  • Devolución de llamada encadenable: modelo avanzado de programación multihilo

Convierta una señal de QObject en un objeto QFuture:

#include "asyncfuture.h"
using namespace AsyncFuture;

// Convert a signal from QObject into a QFuture object

QFuture<void> future = observe(timer, &QTimer::timeout).future();

/* Listen from the future without using QFutureWatcher<T>*/
observe(future).subscribe([]() {
    // onCompleted. It is invoked when the observed future is finished successfully
    qDebug() << "onCompleted";
},[]() {
    // onCanceled
    qDebug() << "onCancel";
});
1
Vladimir Bershov 19 feb. 2020 a las 06:59

Mi idea es usar grupos de subprocesos con un máximo de 1 subproceso disponible para cada uno.

QThreadPool* persistentThread = new QThreadPool; // no need to write custom thread pool
persistentThread->setMaxThreadCount(1);
persistentThread->setExpiryTimeout(-1);

Y entonces

QFuture<int> future_1 = QtConcurrent::run(persistentThread, func_1);
QFuture<int> future_2 = QtConcurrent::run(persistentThread, func_2);

func_2 se ejecutará después de func_1 en el mismo hilo "persistente".

0
Vladimir Bershov 19 feb. 2020 a las 07:34