Pyqt Signals connecting multiple slots
-
I am working on a small program. I noticed significant slowdowns when connecting multiple functions to a single emit call. When I just have one or two functions connected the function call to emit the calls takes around 0.0004 seconds to finish. However when 8 functions are connected this drops to 0.37 seconds a significant time for the UI to be unresponsive.
Am I using signals wrong and should you only connect one or two calls to them? It is something that can be solved with a function that merges the separate calls, I am just mostly interested in improving my understanding of how signals should be used.
Kind regards,
DwarrelQuick test showed this:
0 connected 0.0008
1 connected 0.0004
4 connected 0.148
8 connected 0.37 -
Hi and welcome to devnet,
Did you benchmark these functions ?
Are they all fast or some take more times than others ?
Do they depend on some IOs ?
Which version of PyQt ?
On which OS ? -
Sorry I kept this more general because I assumed it was a general design question.
I did not do a official benchmarking however this has been consistently noticeable. The numbers above are also consistent when manually testing the code.So the time it takes is dependant on the number of functions that have connected to the signal. When I have more "listeners" the "emitter" becomes slower.
Following is snipped of code for the emitter (cycle_widget is a QTreeWidget, with less than 10 items, is is also slow with just 2 items). This treewidget is where we notice the slowdown, the highlighting of the selected cycle has a significant delay, which leads to bad user experience.
def update_cycle_selection(self): self.selected_cycle_ids = list([item.id for item in self.cycle_selector_widget.selectedItems()]) start = time.time() signals.plottab_updated_cycle_selection.emit(self.selected_cycle_ids) print(time.time()-start)
We have 8 plots that are linked and should be updated when cycles are changed. When I reduce this to 1 the speed is significant better.
signals.plottab_updated_cycle_selection.connect(self.force_time_plot.update_selection) signals.plottab_updated_cycle_selection.connect(self.force_extension_plot.update_selection) signals.plottab_updated_cycle_selection.connect(self.contour_time_plot.update_selection)
"Do they depend on some IOs ?" Both emitter function and connected functions do not use IO. However, should that matter? I thought the entire idea of emitter and listeners is that a long lasting task in the listener is not interfering with the execution time of the emitter?
PyQt: 6.7.1
OS tested both Linux & WindowsKind regards,
Tycho -
I am not sure what you are expecting or finding here.
When a signal is emitted, and both signal and slot are in the same thread, emitting a signal directly executes the connected slot(s). (Are you expecting this, or are you thinking that the slots will be executed "in the background/at a later time" while the
emit
returns immediately?) The time taken principally depends on what the slot does. There is some overhead for a signal but it is apparently "small" (there is some article on this I once found). If there are multiple slots attached to a signal then all of these slots must be executed. However, I would not expect the time taken would rise much just because you have multiple slots attached, other than the time in the slots themselves. And in the "common" case the slot content code probably takes much longer than the signal emitting code.However, we discovered in a recent-ish thread in this forum that the Python bindings can take surprisingly long just to call a function, which would be "instantaneous" in C++. The example there was calling something like
QList.count()
IIRC. That thread may have been for PySide, I cannot be sure whether it is similar for PyQt. The overhead has nothing to do with signals/slots per se, it's for calling any function from Python. That OP was calling it thousands or millions of times for it to become noticeable, but it did then become a "considerable" overhead.Your original quoted timings are far too short/quick to give any meaningful statistics. When something takes "0.0008" or "0.0004" (what units? you do not say. seconds??) that is way too small to rely on your timings. Try calling these functions hundreds or thousands of times to get more meaningful timings; even then I am not sure you can rely on accumulation of such a short time period.
-
"I am not sure what you are expecting or finding here."
Well exactly what you gave me, thank you. My assumption was indeed that the emit would be independent of the execution time of a listener (connected function). Knowing that that is not the case completely explains the observed behaviour.
I will optimize the connected functions and see if the connects are always needed. -
"I am not sure what you are expecting or finding here."
Well exactly what you gave me, thank you. My assumption was indeed that the emit would be independent of the execution time of a listener (connected function). Knowing that that is not the case completely explains the observed behaviour.
I will optimize the connected functions and see if the connects are always needed.@Dwarrel said in Pyqt Signals connecting multiple slots:
My assumption was indeed that the emit would be independent of the execution time of a listener (connected function). Knowing that that is not the case completely explains the observed behaviour.
OIC!
The behaviour depends on the parameters to
connect()
and whether the signalling object and the slot object are in the same or different threads.Assuming default --- no extra parameter to
connect()
---- If they are in the same thread signal/slot is executed directly, upon
emit
signal call, i.e. it behaves just like a direct call to the slot(s). Slots are executed consecutively per the order they wereconnect()
ed. Signaller code does not continue until all slots have completed. - If they are in different threads signal causes slot(s) to be executed queued. When slot object thread next enters Qt event loop slot(s) get executed. Order is actually undetermined. Signaller code continues immediately after
emit
, it does not wait. - Extra parameter to
connect()
can be used to alter default behaviour, see docs. You still should not tryDirectConnection
across threads, you could chooseQueuedConnection
if same thread. But non-default is unusual, unless you know what you are doing.
P.S.
I just noticed you are Python. the above is still correct, but there can be extra restrictions with multiple threads to do with Python GIL. But nothing special if all same thread. - If they are in the same thread signal/slot is executed directly, upon