在 Qt 中使用排队连接时如何压缩插槽调用?
在阅读了一些关于 Qt Signal-Slot 通信的文章像这样后,我仍然有关于排队连接的问题.
After reading some articles like this about Qt Signal-Slot communications I still have a question concerning the queued connection.
如果我有一些线程一直在互相发送信号,并且假设一个 thread_slow
在它的事件循环中运行一个慢速方法而另一个 thread_fast
正在运行一个快速发送多个信号而另一个线程仍在运行它的慢方法.....当来自 thread_slow
的慢方法返回到事件循环时,它会处理之前发送的所有信号通过 thread_fast
还是只是最后一个(所有信号都是相同的类型)?
If I have some threads sending signals all the time to each other and lets say one thread_slow
is running a slow method in it's event loop and another thread_fast
is running a fast one that sends multiple signals while the other thread is still running it's slow method.....when the slow method from thread_slow
returns to the event loop, will it process all the signals that were sent before by thread_fast
or just the last one (all the signals are the same type)?
如果它会处理所有的信号,有没有办法让thread_slow
只处理最后一个?(考虑到多线程应用程序中的最后一个"可能含糊不清,为了简单起见,让我们考虑线程请求最后一个信号之前的最后一个信号,因此在线程寻找最后一个信号时发送的新信号可能会丢失).
If it will process all the signals, is it there a way to make the thread_slow
only process the last one? (Considering "the last one" in a multithread application might be vague, let's consider the last signal before the thread asked for the last signal, for the sake of simplicity, so new ones being sent while the thread looks for the last might be lost).
(我问这个是因为我有多个线程从多个线程接收数据,我不希望它们处理旧数据,只是发送的最后一个)
(I am asking this because I have multiple threads receiving data from multiple threads, and I dont want them to process old data, just the last one that was sent)
我已经运行了一些测试,似乎 Qt 会处理所有信号.我做了一个线程:
I have run some tests, and it appears that Qt will process all the signals. I made one thread do:
while(true)
{
QThread::msleep(500);
emit testQueue(test);
test++;
}
另一个插槽可以:
void test::testQueue(int test)
{
test.store(private_test.load() + test);
emit testText(QString("Test Queue: ") + QString::number(private_test.load()));
}
线程将运行:
while(true)
{
QThread::msleep(3000);
QCoreApplication::processEvents();
private_test.store(private_test.load() + 1000);
}
我每 500 毫秒从一个线程向另一个线程发送一个信号,另一个线程休眠 3000 毫秒(3 秒),然后唤醒并将内部变量增加 100.每次执行插槽时,它都会发出带有接收值 + 内部变量的文本.我得到的结果是每次 QCoreApplication::processEvents();
被调用时,所有的信号都会被执行....(我编辑了这部分是因为我在之前的代码中发现了一个错误)
I am sending a signal from one thread to the other every 500 milliseconds, and the other thread sleeps for 3000 milliseconds (3 seconds) and then wakes up and increment an internal variable by 100. every time the slot is executed it emits a text with the value received + the internal variable. The result I am having is that every time QCoreApplication::processEvents();
is called, all signals are executed....(I edited this part because I found a bug in my previous code)
推荐答案
我正在尝试将我的评论转化为答案.我同意您的观点,即文档中缺少这些信息,或者至少对我来说不清楚,显然对您来说也是如此.
I am trying to form my comment into an answer. I agree with you about that the documentation is lacking this information, or at least it is not clear for me, and apparently for you either.
获取更多信息有两种选择:
There would be two options to get more information:
1) 试用
将 qDebug() 或 printf()/fprintf() 语句放入慢"线程中的插槽中,然后查看打印出的内容.运行几次并得出结论.
Put a qDebug() or printf()/fprintf() statement into your slot in the "slow" thread and see what it prints out. Run this a few times and draw the conclusion.
2) 确保
您需要阅读元对象编译器的源代码,也就是.moc 从源文件中获取此信息.这是一个更复杂的调查,但这可能会导致确定性.
You would need to read the source code for this how the meta object compiler, aka. moc gets this through from the source file. This is a bit more involved investigation, but this could lead to certainity.
据我所知,每个信号发射都会发布一个相应的事件.然后,该事件将排队等待线程类中的单独线程.在这里可以找到相关的两个源代码文件:
As far as I know, every signal emission posting a corresponding event. Then, the event will be queued for the separate thread within the thread class. Here you can find the relevant two source code files:
void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
和
class public QPostVEventListector/a>
class QPostEventList : public QVector
有两种权衡方法:
主要优点是在忙操作期间信号不会丢失.但是,这本身可能会较慢,因为它可能会处理比需要更多的操作.
The main advantage is that signals could not be lost during the busy operation. However, this could be inherently slower as it can potentially process a lot more operation than needed.
这个想法是为每个处理的事件重新设置数据,但真正的繁忙操作只排队执行一次.如果有更多事件,它不一定是第一个事件,但这是最简单的实现.
The idea is that the data is re-set for each event handled, but the real busy operation is queued for execution only once. It does not necessarily have to be the for the first event if there are more, but that is the simplest implementation.
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;
}
连接/断开连接
思路是在开始处理的时候,把slot和对应的信号断开.这将确保不会捕获新的信号发射,并在线程有空处理下一个事件时再次将槽连接到信号.
Connect/Disconnect
The idea is to disconnect the slot from the corresponding signal when starting the processing. This will ensure that new signal emission would not be caught, and connect the slot to the signal again once the thread is free to process the next events.
尽管在连接和下一个偶数处理之间,这将在线程中有一些空闲时间,但至少这将是实现它的简单方法.根据此处未真正提供的更多上下文,性能差异实际上甚至可以忽略不计.
This would have some idle time in the thread though between the connection and the next even handled, but at least this would be a simple way of implmeneting it. It may actually be even negligible a performance difference depending on more context not really provided here.
主要缺点是这会在繁忙操作期间丢失信号.
The main drawback is that this would lose the signals during the busy operation.
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&)));
}
未来的想法
我在想是否有一个方便的 API - 例如一个 processEvents() 类似的方法,但有一个参数来处理最后一个发布的事件 - 实际上明确地告诉事件系统处理最后一个而不是绕过问题本身.它看起来确实是这样的 API,但它是私有的.
Future thoughts
I was thinking if there was a convenient API - e.g. a processEvents() alike method, but with an argument to process only the last event posted - for actually telling the event system explicitly to process the last one rather than circumventing the issue itself. It does appear to be such an API, however, it is private.
也许,有人会提交功能请求以公开发布类似内容.
Perhaps, someone will submit a feature request to have something like that in public.
/*!
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)
相关源码可在这里.
它似乎在 QGuiApplication
和 QApplication
中也有一个覆盖版本.
It also seems to have an overriden version in QGuiApplication
and QApplication
.
至于完整性,还有这样的方法:
As for completeness, there is also such a method like this:
void QCoreApplication::removePostedEvents(QObject *receiver, int eventType = 0) [静态]
删除使用 postEvent() 作为接收者发布的给定 eventType 的所有事件.
Removes all events of the given eventType that were posted using postEvent() for receiver.
事件不会被调度,而是从队列中移除.您永远不需要调用此函数.如果您调用它,请注意终止事件可能会导致接收器破坏一个或多个不变量.
The events are not dispatched, instead they are removed from the queue. You should never need to call this function. If you do call it, be aware that killing events may cause receiver to break one or more invariants.
如果接收者为空,则所有对象的 eventType 事件都会被移除.如果 eventType 为 0,则为接收器删除所有事件.永远不要在 eventType 为 0 的情况下调用此函数.如果确实以这种方式调用它,请注意杀死事件可能会导致接收器破坏一个或多个不变量.
If receiver is null, the events of eventType are removed for all objects. If eventType is 0, all the events are removed for receiver. You should never call this function with eventType of 0. If you do call it in this way, be aware that killing events may cause receiver to break one or more invariants.
但是根据文档,这并不是您想要的.
But this is not quite what you would like to have here as per documentation.
相关文章