c++ - que - programa para reducir tamaño de fotos sin perder calidad
Cómo comprimir las llamadas de la ranura al usar la conexión en cola en Qt? (7)
Después de leer algunos artículos como este sobre las comunicaciones de Qt Signal-Slot, todavía tengo una pregunta sobre la conexión en cola.
Si tengo algunos subprocesos que envían señales todo el tiempo entre sí y digamos que un thread_slow
está ejecutando un método lento en su ciclo de eventos y otro thread_fast
está ejecutando uno rápido que envía múltiples señales mientras el otro subproceso todavía está funcionando es un método lento. .... cuando el método lento de thread_slow
regresa al bucle de eventos, ¿procesará todas las señales enviadas antes por thread_fast
o solo la última (todas las señales son del mismo tipo)?
Si procesará todas las señales, ¿hay alguna manera de hacer que thread_slow
solo procese la última ? (Teniendo en cuenta que "el último" en una aplicación de varios hilos puede ser vago, consideremos la última señal antes de que el hilo solicite la última señal, por simplicidad, para que se envíen nuevos mientras el hilo busca el último podría perderse )
(Estoy preguntando esto porque tengo varios subprocesos que reciben datos de varios subprocesos, y no quiero que procesen datos antiguos, solo el último que se envió)
He realizado algunas pruebas, y parece que Qt procesará todas las señales. Hice un hilo:
while(true)
{
QThread::msleep(500);
emit testQueue(test);
test++;
}
y un espacio en otro hará:
void test::testQueue(int test)
{
test.store(private_test.load() + test);
emit testText(QString("Test Queue: ") + QString::number(private_test.load()));
}
y el hilo se ejecutará:
while(true)
{
QThread::msleep(3000);
QCoreApplication::processEvents();
private_test.store(private_test.load() + 1000);
}
Estoy enviando una señal de un hilo a otro cada 500 milisegundos, y el otro hilo duerme durante 3000 milisegundos (3 segundos) y luego se despierta e incrementa una variable interna en 100. Cada vez que se ejecuta el intervalo, emite un texto con el valor recibido + la variable interna. El resultado que estoy teniendo es que cada vez QCoreApplication::processEvents();
se llama, todas las señales se ejecutan .... (Edité esta parte porque encontré un error en mi código anterior)
QCoreApplication QMetaCallEvent Compression
Cada llamada de ranura en cola termina en la publicación de un QMetaCallEvent
en el objeto de destino. El evento contiene el objeto emisor, la identificación de señal, el índice de ranura y los parámetros de llamada empaquetada. En Qt 5, la identificación de la señal generalmente no es igual al valor devuelto por QMetaObject::signalIndex()
: es un índice calculado como si el objeto solo tuviese métodos de señal y ningún otro método.
El objetivo es comprimir tales llamadas de modo que solo exista una única llamada en la cola de eventos para una tupla dada de (objeto emisor, señal del remitente, objeto receptor, ranura del receptor).
Esta es la única forma sensata de hacerlo, sin tener que realizar cambios en los objetos de origen o destino, y al mismo tiempo mantener una sobrecarga mínima. Los métodos de recursión de ciclo de eventos en mis otras respuestas tienen una sobrecarga de la pila seria por cada evento, del orden de 1kbyte cuando Qt está construido para arquitecturas de puntero de 64 bits.
Se puede acceder a la cola de eventos cuando se publican nuevos eventos en un objeto que tiene uno o más eventos ya publicados . En tal caso, QCoreApplication::postEvent
llama a QCoreApplication::compressEvent
. compressEvent
no se llama cuando el primer evento se publica en un objeto. En una reimplementación de este método, el contenido de un QMetaCallEvent
publicado en el objeto de destino se puede verificar para una llamada a su ranura, y el duplicado obsoleto tiene que ser eliminado. QMetaCallEvent
QPostEvent
encabezados de Qt privados para obtener las definiciones de QMetaCallEvent
, QPostEvent
y QPostEventList
.
Pros: Ni el remitente ni el receptor tienen que ser conscientes de nada. Las señales y las ranuras funcionan tal como están, incluidas las llamadas al puntero de método en Qt 5. Qt usa esta forma de comprimir eventos.
Contras: Requiere la inclusión de encabezados Qt privados y el borrado forzado de la bandera QEvent::posted
.
En lugar de piratear el QEvent::posted
, los eventos que se van a eliminar se pueden poner en cola en una lista separada y eliminarse fuera de la llamada compressEvent
, cuando se dispara un temporizador de duración cero. Esto tiene la sobrecarga de una lista adicional de eventos y cada eliminación de evento iterando a través de la lista de eventos publicada .
Otros enfoques
El objetivo de hacerlo de otra manera no es usar las partes internas de Qt.
L1 La primera limitación es no tener acceso a los contenidos de QMetaCallEvent
definido de forma privada. Se puede tratar de la siguiente manera:
Un objeto proxy con señales y ranuras de las mismas firmas que las del objetivo se puede conectar entre los objetos de origen y de destino.
La ejecución de
QMetaCallEvent
en un objeto proxy permite la extracción del tipo de llamada, la identificación de la ranura llamada y los argumentos.En lugar de las conexiones de ranura de señal, los eventos se pueden publicar explícitamente en el objeto de destino. El objeto de destino, o un filtro de evento, debe volver a sintetizar explícitamente la llamada de ranura desde los datos del evento.
Se puede usar una implementación personalizada de
compressedConnect
en lugar deQObject::connect
. Esto expone por completo los detalles de la señal y la ranura. Un objeto proxy se puede utilizar para realizar un equivalente dequeued_activate
dequeued_activate
en el lado del objeto emisor.
L2 La segunda limitación no es poder volver a QCoreApplication::compressEvent
completamente QCoreApplication::compressEvent
, ya que la lista de eventos se define de forma privada. Todavía tenemos acceso al evento que se está comprimiendo, y aún podemos decidir si eliminarlo o no, pero no hay forma de repetir la lista de eventos. Así:
Se puede acceder implícitamente a la cola de eventos mediante la invocación recursiva de
sendPostedEvents
desde dentro desendPostedEvents
(y también desdeeventFilter()
,event()
o desde las ranuras). Esto no causa un interbloqueo, ya queQCoreApplication::sendPostedEvents
no puede (y no mantiene) un mutex de bucle de evento mientras el evento se entrega a través desendEvent
. Los eventos se pueden filtrar de la siguiente manera:- globalmente en una
QCoreApplication::notify
reimplementada, - globalmente al registrar un
QInternal::EventNotifyCallback
, - localmente al adjuntar un filtro de eventos a los objetos,
- explícitamente localmente mediante la reimplementación de
QObject::event()
en la clase objetivo.
Los eventos duplicados todavía se publican en la cola del evento. Las llamadas recursivas para
notify
desdesendPostedEvents
consumen bastante espacio de pila (presupuesto de 1kb en arquitecturas de puntero de 64 bits).- globalmente en una
Los eventos ya presentes se pueden eliminar llamando a
QCoreApplication::removePostedEvents
antes de publicar un evento nuevo en un objeto. Desafortunadamente, hacer esto dentro deQCoreApplication::compressEvent
causa un interbloqueo ya que el mutex de la cola de eventos ya está guardado.Una clase de evento personalizado que incluye el puntero al objeto receptor puede llamar automáticamente a
removePostedEvents
en el constructor.Los eventos comprimidos existentes, como
QEvent::Exit
, se pueden volver a asignar.El conjunto de esos eventos es un detalle de implementación y podría cambiar. Qt no discrimina entre esos eventos más que por el puntero
QObject
del receptor. Una implementación requiere la sobrecarga de un QObjeto proxy por cada tupla (tipo de evento, objeto receptor).
Implementación
El siguiente código funciona tanto en Qt 4 como en Qt 5. En este último caso, asegúrese de agregar QT += core-private
a su archivo de proyecto qmake, de modo que los encabezados de Qt privados se incluyan.
Las implementaciones que no usan los encabezados internos de Qt se dan en otras respuestas:
- Usando L1.1 y L2.1 .
- Usando L1.2 y L2.1 .
Hay dos rutas de código de eliminación de eventos, seleccionadas por if (true)
. La ruta del código habilitado conserva el evento más reciente y tiene más sentido, por lo general. Alternativamente, es posible que desee conservar el evento más antiguo: eso es lo que hace la ruta del código deshabilitado.
#include <QApplication>
#include <QMap>
#include <QSet>
#include <QMetaMethod>
#include <QMetaObject>
#include <private/qcoreapplication_p.h>
#include <private/qthread_p.h>
#include <private/qobject_p.h>
#include <QWidget>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QSpinBox>
#include <QFormLayout>
// Works on both Qt 4 and Qt 5.
//
// Common Code
/*! Keeps a list of singal indices for one or more meatobject classes.
* The indices are signal indices as given by QMetaCallEvent.signalId.
* On Qt 5, those do *not* match QMetaObject::methodIndex since they
* exclude non-signal methods. */
class SignalList {
Q_DISABLE_COPY(SignalList)
typedef QMap<const QMetaObject *, QSet<int> > T;
T m_data;
/*! Returns a signal index that is can be compared to QMetaCallEvent.signalId. */
static int signalIndex(const QMetaMethod & method) {
Q_ASSERT(method.methodType() == QMetaMethod::Signal);
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
int index = -1;
const QMetaObject * mobj = method.enclosingMetaObject();
for (int i = 0; i <= method.methodIndex(); ++i) {
if (mobj->method(i).methodType() != QMetaMethod::Signal) continue;
++ index;
}
return index;
#else
return method.methodIndex();
#endif
}
public:
SignalList() {}
void add(const QMetaMethod & method) {
m_data[method.enclosingMetaObject()].insert(signalIndex(method));
}
void remove(const QMetaMethod & method) {
T::iterator it = m_data.find(method.enclosingMetaObject());
if (it != m_data.end()) {
it->remove(signalIndex(method));
if (it->empty()) m_data.erase(it);
}
}
bool contains(const QMetaObject * metaObject, int signalId) {
T::const_iterator it = m_data.find(metaObject);
return it != m_data.end() && it.value().contains(signalId);
}
};
//
// Implementation Using Event Compression With Access to Private Qt Headers
struct EventHelper : private QEvent {
static void clearPostedFlag(QEvent * ev) {
(&static_cast<EventHelper*>(ev)->t)[1] &= ~0x8001; // Hack to clear QEvent::posted
}
};
template <class Base> class CompressorApplication : public Base {
SignalList m_compressedSignals;
public:
CompressorApplication(int & argc, char ** argv) : Base(argc, argv) {}
void addCompressedSignal(const QMetaMethod & method) { m_compressedSignals.add(method); }
void removeCompressedSignal(const QMetaMethod & method) { m_compressedSignals.remove(method); }
protected:
bool compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents) {
if (event->type() != QEvent::MetaCall)
return Base::compressEvent(event, receiver, postedEvents);
QMetaCallEvent *mce = static_cast<QMetaCallEvent*>(event);
if (! m_compressedSignals.contains(mce->sender()->metaObject(), mce->signalId())) return false;
for (QPostEventList::iterator it = postedEvents->begin(); it != postedEvents->end(); ++it) {
QPostEvent &cur = *it;
if (cur.receiver != receiver || cur.event == 0 || cur.event->type() != event->type())
continue;
QMetaCallEvent *cur_mce = static_cast<QMetaCallEvent*>(cur.event);
if (cur_mce->sender() != mce->sender() || cur_mce->signalId() != mce->signalId() ||
cur_mce->id() != mce->id())
continue;
if (true) {
/* Keep The Newest Call */
// We can''t merely qSwap the existing posted event with the new one, since QEvent
// keeps track of whether it has been posted. Deletion of a formerly posted event
// takes the posted event list mutex and does a useless search of the posted event
// list upon deletion. We thus clear the QEvent::posted flag before deletion.
EventHelper::clearPostedFlag(cur.event);
delete cur.event;
cur.event = event;
} else {
/* Keep the Oldest Call */
delete event;
}
return true;
}
return false;
}
};
//
// Demo GUI
class Signaller : public QObject {
Q_OBJECT
public:
Q_SIGNAL void emptySignal();
Q_SIGNAL void dataSignal(int);
};
class Widget : public QWidget {
Q_OBJECT
QPlainTextEdit * m_edit;
QSpinBox * m_count;
Signaller m_signaller;
Q_SLOT void emptySlot() {
m_edit->appendPlainText("emptySlot invoked");
}
Q_SLOT void dataSlot(int n) {
m_edit->appendPlainText(QString("dataSlot(%1) invoked").arg(n));
}
Q_SLOT void sendSignals() {
m_edit->appendPlainText(QString("/nEmitting %1 signals").arg(m_count->value()));
for (int i = 0; i < m_count->value(); ++ i) {
emit m_signaller.emptySignal();
emit m_signaller.dataSignal(i + 1);
}
}
public:
Widget(QWidget * parent = 0) : QWidget(parent),
m_edit(new QPlainTextEdit), m_count(new QSpinBox)
{
QFormLayout * l = new QFormLayout(this);
QPushButton * invoke = new QPushButton("Invoke");
m_edit->setReadOnly(true);
m_count->setRange(1, 1000);
l->addRow("Number of slot invocations", m_count);
l->addRow(invoke);
l->addRow(m_edit);
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
connect(invoke, &QPushButton::clicked, this, &Widget::sendSignals);
connect(&m_signaller, &Signaller::emptySignal, this, &Widget::emptySlot, Qt::QueuedConnection);
connect(&m_signaller, &Signaller::dataSignal, this, &Widget::dataSlot, Qt::QueuedConnection);
#else
connect(invoke, SIGNAL(clicked()), SLOT(sendSignals()));
connect(&m_signaller, SIGNAL(emptySignal()), SLOT(emptySlot()), Qt::QueuedConnection);
connect(&m_signaller, SIGNAL(dataSignal(int)), SLOT(dataSlot(int)), Qt::QueuedConnection);
#endif
}
};
int main(int argc, char *argv[])
{
CompressorApplication<QApplication> a(argc, argv);
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
a.addCompressedSignal(QMetaMethod::fromSignal(&Signaller::emptySignal));
a.addCompressedSignal(QMetaMethod::fromSignal(&Signaller::dataSignal));
#else
a.addCompressedSignal(Signaller::staticMetaObject.method(Signaller::staticMetaObject.indexOfSignal("emptySignal()")));
a.addCompressedSignal(Signaller::staticMetaObject.method(Signaller::staticMetaObject.indexOfSignal("dataSignal(int)")));
#endif
Widget w;
w.show();
return a.exec();
}
#include "main.moc"
Este es otro enfoque más portátil para Qt 4 y Qt 5, y que no requiere acceso a las partes internas de Qt (aparte de lo que está disponible en los encabezados públicos). En Qt 5, solo se admiten las conexiones de estilo Qt 4. Las entidades comprimidas son pares (objeto receptor, ranura). Esto es diferente de la tupla (remitente, receptor, señal, ranura) utilizada cuando se tiene acceso completo a QMetaCallEvent
.
Utiliza QObject::qt_metacall
para husmear los detalles de la llamada desde el recuadro negro QMetaCallEvent
. Se usa la recursión en sendPostedEvents
, al igual que en mi otra respuesta no interna .
Vale la pena señalar que la API de QObject::qt_metacall
ha mantenido sin cambios desde al menos Qt 4.0.
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QSpinBox>
#include <QFormLayout>
#include <QSet>
#include <QMetaMethod>
// Common Code
/*! Keeps a list of method indices for one or more meatobject classes. */
class MethodList {
Q_DISABLE_COPY(MethodList)
typedef QMap<const QMetaObject *, QSet<int> > T;
T m_data;
public:
MethodList() {}
template <class T> void add(const char * slot) {
add(T::staticMetaObject.method(T::staticMetaObject.indexOfSlot(slot)));
}
void add(const QMetaMethod & method) {
Q_ASSERT(method.methodIndex() >= 0);
m_data[method.enclosingMetaObject()].insert(method.methodIndex());
}
void remove(const QMetaMethod & method) {
T::iterator it = m_data.find(method.enclosingMetaObject());
if (it != m_data.end()) {
it->remove(method.methodIndex());
if (it->empty()) m_data.erase(it);
}
}
bool contains(const QMetaObject * metaObject, int methodId) {
T::const_iterator it = m_data.find(metaObject);
return it != m_data.end() && it.value().contains(methodId);
}
};
Q_GLOBAL_STATIC(MethodList, compressedSlots)
// Compressor
class Compressor : public QObject {
enum { Idle, Armed, Valid } m_state;
QMetaObject::Call m_call;
int m_methodIndex;
QSet<int> m_armed; // armed method IDs
int qt_metacall(QMetaObject::Call call, int id, void ** args) {
if (m_state != Armed) return QObject::qt_metacall(call, id, args);
m_state = Valid;
m_call = call;
m_methodIndex = id;
return 0;
}
bool eventFilter(QObject * target, QEvent * ev) {
Q_ASSERT(target == parent());
if (ev->type() == QEvent::MetaCall) {
m_state = Armed;
if (QT_VERSION < QT_VERSION_CHECK(5,0,0) || ! *(void**)(ev+1)) {
// On Qt5, we ensure null QMetaCallEvent::slotObj_ since we can''t handle Qt5-style member pointer calls
Compressor::event(ev); // Use QObject::event() and qt_metacall to extract metacall data
}
if (m_state == Armed) m_state = Idle;
// Only intercept compressed slot calls
if (m_state != Valid || m_call != QMetaObject::InvokeMetaMethod ||
! compressedSlots()->contains(target->metaObject(), m_methodIndex)) return false;
int methodIndex = m_methodIndex;
m_armed.insert(methodIndex);
QCoreApplication::sendPostedEvents(target, QEvent::MetaCall); // recurse
if (! m_armed.contains(methodIndex)) return true; // Compress the call
m_armed.remove(methodIndex);
}
return false;
}
public:
Compressor(QObject * parent) : QObject(parent), m_state(Idle) {
parent->installEventFilter(this);
}
};
//
// Demo GUI
class Signaller : public QObject {
Q_OBJECT
public:
Q_SIGNAL void emptySignal();
Q_SIGNAL void dataSignal(int);
};
class Widget : public QWidget {
Q_OBJECT
QPlainTextEdit * m_edit;
QSpinBox * m_count;
Signaller m_signaller;
Q_SLOT void emptySlot() {
m_edit->appendPlainText("emptySlot invoked");
}
Q_SLOT void dataSlot(int n) {
m_edit->appendPlainText(QString("dataSlot(%1) invoked").arg(n));
}
Q_SLOT void sendSignals() {
m_edit->appendPlainText(QString("/nEmitting %1 signals").arg(m_count->value()));
for (int i = 0; i < m_count->value(); ++ i) {
emit m_signaller.emptySignal();
emit m_signaller.dataSignal(i + 1);
}
}
public:
Widget(QWidget * parent = 0) : QWidget(parent),
m_edit(new QPlainTextEdit), m_count(new QSpinBox)
{
QFormLayout * l = new QFormLayout(this);
QPushButton * invoke = new QPushButton("Invoke");
m_edit->setReadOnly(true);
m_count->setRange(1, 1000);
l->addRow("Number of slot invocations", m_count);
l->addRow(invoke);
l->addRow(m_edit);
connect(invoke, SIGNAL(clicked()), SLOT(sendSignals()));
m_edit->appendPlainText(QString("Qt %1").arg(qVersion()));
connect(&m_signaller, SIGNAL(emptySignal()), SLOT(emptySlot()), Qt::QueuedConnection);
connect(&m_signaller, SIGNAL(dataSignal(int)), SLOT(dataSlot(int)), Qt::QueuedConnection);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
compressedSlots()->add<Widget>("emptySlot()");
compressedSlots()->add<Widget>("dataSlot(int)");
Widget w;
new Compressor(&w);
w.show();
return a.exec();
}
#include "main.moc"
Este es otro enfoque. No requiere cambios en el remitente ni en los objetos del receptor, pero requiere un objeto CompressorProxy
personalizado. Esto es portátil tanto para Qt 4 como para Qt 5, y no requiere acceso a las partes internas de Qt.
El objeto del compresor debe ser un elemento secundario del objeto de destino, el que tiene las ranuras. De esa forma rastrea el hilo del objeto objetivo. Dado que las señales del compresor están conectadas a las ranuras del objetivo, cuando están en el mismo subproceso no hay una sobrecarga de conexiones en cola para las llamadas de ranura de destino.
La magia ocurre en el método emitCheck
: se llama recursivamente.
- Una llamada de ranura termina en
emitCheck
. - Otros eventos publicados se envían llamando a
sendPostedEvents
. - Si hay llamadas duplicadas en la cola de eventos, terminarán en
emitCheck
nuevamente. - Una vez que se selecciona el último evento en la cola, y
sendPostedEvents
ya no se repite, se restablece un indicador para la ranura dada, de modo que su señal de proxy no se emita más de una vez. Ese es el comportamiento de compresión deseado.
Para cualquier conjunto dado de llamadas de tragamonedas en cola a una instancia de CompressorProxy
, emitCheck
devolverá true
solo una vez para un tramo que fue llamado varias veces en un pase a través de la lista de eventos publicada.
Tenga en cuenta que el uso de la pila por llamada recursiva en el modo de lanzamiento es de alrededor de 600 bytes en las arquitecturas de 32 bits, y el doble que en las arquitecturas de 64 bits. En modo de depuración en OS X, utilizando una compilación de 64 bits, el uso de la pila por recursión es ~ 4kb.
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QSpinBox>
#include <QFormLayout>
class CompressorProxy : public QObject {
Q_OBJECT
bool emitCheck(bool & flag) {
flag = true;
QCoreApplication::sendPostedEvents(this, QEvent::MetaCall); // recurse
bool result = flag;
flag = false;
return result;
}
bool m_slot;
Q_SLOT void slot() {
if (emitCheck(m_slot)) emit signal();
}
Q_SIGNAL void signal();
bool m_slot_int;
Q_SLOT void slot_int(int arg1) {
if (emitCheck(m_slot_int)) emit signal_int(arg1);
}
Q_SIGNAL void signal_int(int);
public:
// No default constructor, since the proxy must be a child of the
// target object.
explicit CompressorProxy(QObject * parent) : QObject(parent) {}
};
//
// Demo GUI
class Signaller : public QObject {
Q_OBJECT
public:
Q_SIGNAL void emptySignal();
Q_SIGNAL void dataSignal(int);
};
class Widget : public QWidget {
Q_OBJECT
QPlainTextEdit * m_edit;
QSpinBox * m_count;
Signaller m_signaller;
Q_SLOT void emptySlot() {
m_edit->appendPlainText("emptySlot invoked");
}
Q_SLOT void dataSlot(int n) {
m_edit->appendPlainText(QString("dataSlot(%1) invoked").arg(n));
}
Q_SLOT void sendSignals() {
m_edit->appendPlainText(QString("/nEmitting %1 signals").arg(m_count->value()));
for (int i = 0; i < m_count->value(); ++ i) {
emit m_signaller.emptySignal();
emit m_signaller.dataSignal(i + 1);
}
}
public:
Widget(QWidget * parent = 0) : QWidget(parent),
m_edit(new QPlainTextEdit), m_count(new QSpinBox)
{
QFormLayout * l = new QFormLayout(this);
QPushButton * invoke = new QPushButton("Invoke");
m_edit->setReadOnly(true);
m_count->setRange(1, 1000);
l->addRow("Number of slot invocations", m_count);
l->addRow(invoke);
l->addRow(m_edit);
connect(invoke, SIGNAL(clicked()), SLOT(sendSignals()));
m_edit->appendPlainText(QString("Qt %1").arg(qVersion()));
CompressorProxy * proxy = new CompressorProxy(this);
connect(&m_signaller, SIGNAL(emptySignal()), proxy, SLOT(slot()), Qt::QueuedConnection);
connect(&m_signaller, SIGNAL(dataSignal(int)), proxy, SLOT(slot_int(int)), Qt::QueuedConnection);
connect(proxy, SIGNAL(signal()), this, SLOT(emptySlot()));
connect(proxy, SIGNAL(signal_int(int)), this, SLOT(dataSlot(int)));
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "main.moc"
Estoy tratando de formar mi comentario en una respuesta. Estoy de acuerdo con usted acerca de que la documentación carece de esta información, o al menos no está clara para mí, y aparentemente también para usted.
Habría dos opciones para obtener más información:
1) Prueba
Coloque una instrucción qDebug () o printf () / fprintf () en su ranura en el subproceso "lento" y vea lo que imprime. Ejecuta esto unas cuantas veces y saca la conclusión.
2) Asegurándose
Tendría que leer el código fuente para esto, como el compilador de metaobjetos, aka. moc obtiene esto a través del archivo fuente. Esta es una investigación un poco más complicada, pero esto podría conducir a la certeza.
Por lo que sé, cada emisión de señal publica un evento correspondiente. Luego, el evento se pondrá en cola para el hilo separado dentro de la clase de subprocesos. Aquí puede encontrar los dos archivos de código fuente relevantes:
void QCoreApplication :: postEvent (receptor QObject *, evento QEvent *, prioridad int)
y
clase QPostEventList: public QVector
Hay dos enfoques con sus intercambios:
Ponga en cola una operación de ranura ocupada desde la ranura del transformador de datos
La principal ventaja es que las señales no pueden perderse durante la operación ocupada. Sin embargo, esto podría ser inherentemente más lento, ya que puede potencialmente procesar muchas más operaciones de las necesarias.
La idea es que los datos se vuelvan a configurar para cada evento que se maneje, pero la operación real ocupada se pone en cola para su ejecución solo una vez. No necesariamente tiene que ser el para el primer evento si hay más, pero esa es la implementación más simple.
Foo::Foo(QObject *parent) : QObject(parent)
{
...
connect(this, SIGNAL(dataUpdateSignal(const QByteArray&)), SLOT(dataUpdateSlot(const QByteArray&)));
connect(this, SIGNAL(queueBusyOperationSignal()), SLOT(busyOperation()));
...
}
void Foo::dataUpdateSlot(const QByteArray &data)
{
m_data = data;
if (busyOperationQueued);
emit queueBusyOperationSignal();
m_busyOperationQueued = true;
}
}
void MyClass::busyOperationSlot()
{
// Do the busy work with m_data here
m_busyOperationQueued = false;
}
Conectar / Desconectar
La idea es desconectar la ranura de la señal correspondiente al iniciar el procesamiento. Esto asegurará que la nueva emisión de señal no sea capturada, y conecta la ranura a la señal nuevamente una vez que el hilo esté libre para procesar los próximos eventos.
Esto tendría algún tiempo de inactividad en el hilo, aunque entre la conexión y el siguiente incluso se maneja, pero al menos esta sería una manera simple de implmeneting. En realidad, puede ser incluso insignificante una diferencia de rendimiento en función de un contexto más no proporcionado aquí.
El principal inconveniente es que esto perdería las señales durante la operación ocupada.
Foo::Foo(QObject *parent) : QObject(parent)
{
...
connect(this, SIGNAL(dataUpdateSignal(const QByteArray&)), SLOT(busyOperationSlot(const QByteArray&)));
...
}
void MyClass::busyOperationSlot(const QByteArray &data)
{
disconnect(this, SIGNAL(dataUpdateSignal(const QByteArray&)), this, SLOT(dataUpdateSlot(const QByteArray&)));
// Do the busy work with data here
connect(this, SIGNAL(dataUpdateSignal(const QByteArray&)), SLOT(dataUpdateSlot(const QByteArray&)));
}
Pensamientos futuros
Estaba pensando si había una API conveniente, por ejemplo, un método similar a processEvents (), pero con un argumento para procesar solo el último evento publicado, para decirle al sistema de eventos que procese explícitamente el último en lugar de eludir el problema en sí. Parece ser una API así, sin embargo, es privada.
Quizás, alguien envíe una solicitud de función para tener algo así en público.
/*!
/internal
Returns /c true if /a event was compressed away (possibly deleted) and should not be added to the list.
*/
bool QCoreApplication::compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents)
El código fuente relevante se puede encontrar here .
También parece tener una versión modificada en QGuiApplication
y QApplication
.
En cuanto a la integridad, también hay un método como este:
void QCoreApplication :: removePostedEvents (receptor QObject *, int eventType = 0) [estática]
Elimina todos los eventos del eventType dado que se publicaron usando postEvent () para el receptor.
Los eventos no se envían, sino que se eliminan de la cola. Nunca deberías necesitar llamar a esta función. Si lo llamas, ten en cuenta que matar eventos puede hacer que el receptor rompa una o más invariantes.
Si el receptor es nulo, los eventos de eventType se eliminan para todos los objetos. Si eventType es 0, todos los eventos se eliminan para el receptor. Nunca debe llamar a esta función con eventType de 0. Si lo llama de esta manera, tenga en cuenta que matar eventos puede hacer que el receptor rompa una o más invariantes.
Pero esto no es exactamente lo que le gustaría tener aquí según la documentación.
From question: "If it will process all the signals, is it there a way to make the thread_slow only process the last one?"
If you just want to always get the last signal processed, and don''t mind if few extra signals get processed as long as it does not make things slow, then you could try a very simple approach like this, using the regular QThread::exec()
event loop. Put these slot methods into a QObject
subclass, which you then move to a thread:
//slot
void MyClass::publicReceiverSlotForQueuedSignals(QString data)
{
// Update data every time
mReceivedData = data;
// Allow worker method to be queued just once
if (!mWorkerSlotInvoked) {
mWorkerSlotInvoked = true;
QMetaObject::invokeMethod(this, "workerSlot", Qt::QueuedConnection);
qDebug() << "publicReceiverSlotForQueuedSignals: invoked workerSlot!"
<< "New data:" << mReceivedData;
} else {
qDebug() << "publicReceiverSlotForQueuedSignals: workerSlot already invoked."
<< "New data:" << mReceivedData;
}
}
//slot
void MyClass::privateWorkerSlot()
{
mWorkerSlotInvoked = false;
qDebug() << "workerSlot for data:" << mReceivedData;
QThread::msleep(3000);
qDebug() << "workerSlot returning.";
}
The publicReceiverSlotForQueuedSignals
goes through very fast ( qDebug
in else
is probably the most time consuming part for rapid calls), so it doesn''t really matter how many signals are queued. And then privateWorkerSlot
will get invoked just one per event loop rotation of that thread, no matter how slowly it goes.
Also it would be trivial to add a mutex to protect mReceivedData
and mWorkerSlotInvoked
in both slot methods (and everywhere else you might use them). Then you could make a direct connection to the slot, because invokeMethod
is thread safe, and mutex would make handling the private data members of MyClass
thread safe as well. Just make sure you copy the contents of mReceivedData
to a local variable and unlock the mutex, before doing the time consuming processing of it.
Note: untested code, probably has a few mistakes.
You can use the combination of DirectConnection and QueueConnection :
On your Worker side (
thread_slow
):A public slot, that is intended to be called by your task provider (
thread_fast
)void Working::process() { if (working) { printf("Drop a task %p/n", QThread::currentThread()); return; } working = true; emit realWork(); }
A processing function (that is slow) :
realProcess()
void Working::realProcess() { printf(" **** Working ... %p/n",QThread::currentThread()); fflush(stdout); // Emulate a big processing ... usleep(3000*1000); printf(" **** Done. %p/n",QThread::currentThread());fflush(stdout); working = false; emit done(); }
A QueueConnection from
realWork
torealProcess
Working::Working() { working = false; connect(this,SIGNAL(realWork()),this,SLOT(realProcess()),Qt::QueuedConnection); }
On your task provider side (
thread_fast
)A
startWork()
signalvoid TaskProv::orderWork() { emit startWork(); }
A DirectConnection to the worker process slot
QObject::connect(&taskProvider,SIGNAL(startWork()),&worker,SLOT(process()),Qt::DirectConnection);
Some notes:
The function
Working::process()
will be ran in thethread_fast
(even it is a worker member function) but it just check for a flag so it should not impact the processing timeSi le importa una posible tarea adicional, puede proteger la bandera de trabajo del trabajador con mutex para una administración más estricta.
Esto es muy similar a la operación "Queue a busy busy slot from the data mutation slot" de lpapp, excepto que el tipo de conexión debe ser una combinación correcta de Direct y Queue.
thread_slow
procesará todas las señales enviadas en su bucle de evento, si utilizó la conexión de cola o postEvent
Fuente:
Conexión en cola La ranura se invoca cuando el control regresa al bucle de evento del hilo del receptor. La ranura se ejecuta en el hilo del receptor.
Si desea obtener más detalles sobre cómo se procesa el evento, puede mirar aquí:
Como puede ver, los eventos se ordenan en orden de prioridad, por lo que si todos sus eventos tienen la misma prioridad, primero se ingresa primero.
No es una tarea trivial, aquí un intento áspero, dime si funciona.
Lo que sugiero es básicamente almacenar los eventos usted mismo y procesar solo el último.
thread_slow.h
int current_val;
bool m_isRunning;
thread_slow.cpp
void enqueue_slot( int val /*or whatever you value is*/ ) {
// You''ll enventually need a a QMutex here if your slot is not call in the thread
m_current_val = val;
if( !m_isRunning )
slowRun();
}
void checkHasReceivedEventSlot() {
if( m_current_val != -1 ) // Invalid value or a test condition
slowRun();
}
void slowRun() {
m_isRunning = true;
int v = m_current_val;
m_current_val = -1; // Invalid value
// Do stuff with v
// Let the queue fill itself with enqueue_slot calls
QTimer::singleShot(kTIMEOUT, this, SLOT(checkHasReceivedEventSlot()));
}
La primera vez que se llama a enqueue_slot, se iniciará la ejecución lenta
EDITAR:
Para asegurarse de que sea el último evento, quizás pueda hacer lo siguiente:
void checkHasReceivedEventSlot() {
// Runs enqueue_slot until no more events are in the loop
while( m_thread->eventDispatcher()->hasPendingEvents() )
m_thread->eventDispatcher()->processEvents(QEventLoop::AllEvents);
// m_current_val should hold the last event
if( m_current_val != -1 ) // Invalid value or a test condition
slowRun();
}