3

Consider the following situation:

class Foo : public QObject {
  Q_OBJECT
  public:
    void set_A(int a) { emit updated(this); } 
    void set_B(int b) { emit updated(this); }
  signals: 
    void updated(Foo*);
}

Foo f;
connect(&f, SIGNAL(updated(Foo*)), something, SLOT(do_something_heavy(Foo*)), Qt::QueuedConnection)

void bar() { 
  f.set_A(5); f.set_B(6);
}

How can I ensure that only one of the signals reach the do_something_heavy() call?

I want to be able to use set_A() and invoke the do_something_heavy(), but in the case when both set_A and set_B are invoked, I don't want to do_something_heavy() twice.

Can I unqueue all remaining outstanding signals for that particular sender/receiver pair? Preferably at emit, rather than at receive, but that's just for the sake of brevity and encapsulation - I want the updated(Foo*) to signify the need to change the local receiver's state, and if the connection is queued, the semantics are such that I don't need the update to occur twice.

Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
qdot
  • 6,195
  • 5
  • 44
  • 95

3 Answers3

4

I've found it useful to use a QTimer to combine signal invocations.

Foo f;
QTimer timer;
timer.setInterval(0); // this could be set to whatever.
timer.setSingleShot(true);

connect(&f, SIGNAL(updated()),
        &timer, SLOT(start()));
connect(&timer, SIGNAL(timeout()),
        something, SLOT(do_something_heavy()));
Utkarsh
  • 1,492
  • 1
  • 11
  • 19
2

I can think of three different options for this scenario. All options don't require the connection to be queued, as they guarantee that the signal gets emitted only once.

Option 1: Define something like a flush() method

The flush method then emits the updated signal, while the setter methods don't. The client code will have to manually call flush().

Option 2: Emit the signal when the program goes idle

This is only a solution if lazy updating is OK. This means, only when your application goes idle, the signal gets emitted. But since you use queued connections, this is already the case.

This is highly dynamic, giving you the possibility to even change the two parameters multiple times and the expensive update operation only gets called once.

To achieve this, in the setter methods you simply set a private boolean variable changed to true, and start a single shot timer (with zero timeout) to a special slot in class Foo, let's say the slot emitUpdated(). Note that the single shot timer will call your slot as often as a setter method was called, so the slot has to care about this. It simply checks if changed is true, emits the real signal and sets changed to false. So the signal only gets emitted once.

Note that this is already queued! The single shot timer will be put in the event queue (multiple times) and call the emitUpdated to emit the actual signal once. So you might want to connect directly to this signal to avoid the double penalty that comes with queued connections.

Option 3: Make the assumption that the setters are called in the same order from every client code

This results in the best performance. Make sure that you always call setA, then setB, and emit the signal only in setB. Only an option for release code (make some asserts that verify this "protocol") and if you always want to call both methods.

leemes
  • 44,967
  • 21
  • 135
  • 183
  • Yeah. Option 2 is similar to what I have figured out on my own, just with a QMap<> version of the 'changed' since I want the caller/callee semantics - I want the do_something_heavy() called once per each caller, just not for each caller's method - so the signal processing is actually on every receiver end.. which is ugly. I'm seriously thinking about potentially exploring QSignalMapper and subclassing it? – qdot Oct 09 '12 at 21:13
  • Could be worth a try, but you should think this through before you start coding it, since it will get a bit complicated. It is possible that QSignalMapper will not work at all here. I'm too tired now to think it through ;) – leemes Oct 09 '12 at 21:15
  • I think it's the only way forward - the 'do_something_heavy()' function is responsible for GUI updates (and sit in the GUI thread) in the event one of the worker threads decides to update some shared state - so I want it to process the changes, but just once should the GUI loop get overloaded. – qdot Oct 09 '12 at 21:18
  • What I really wished for was something along the lines (given that the queuing happens in the receiving QObject) of "dequeueAllFrom(QObject *caller)" API call.. but it's too much to ask for. – qdot Oct 09 '12 at 21:19
0

What you can do is, disconnect the slot in do something_heavy().

i.e. when you start the execution of do something_heavy, disconnect the slot. and once the execution is complete, re-connect the slot. This way, while the execution is going on, no further signal will be processed. Though signal will be emitted but as there is no corresponding slot connected, it will not execute again.

Murtuza Kabul
  • 6,438
  • 6
  • 27
  • 34
  • Thanks, that is an idea - but I wonder if that would even clear the currently outstanding queue - I do want to receive *new* signals should they arrive during doing something heavy, but I don't want to receive the same signal from the same sender, in few copies in the same iteration of the event loop - one is sufficient. – qdot Oct 09 '12 at 16:22
  • In that case, you can disconnect a particular sender once it sends the signal so others can still send the signal. – Murtuza Kabul Oct 09 '12 at 16:25
  • I don't know exactly how QT handles the signal queue and I think it is platform specific logic. May be you are trying to fiddle with signal queue. – Murtuza Kabul Oct 09 '12 at 16:26