Hey @Mr_S_J_Childerley welcome the forum!
Yep QTimer
can only be started on a thread which has an event loop, because when the timer fires (timeout or interval) there needs to be an event queue to put that event onto. Another odd peculiarity of QTimer
is that when you create an interval timer you need to keep a reference to it. But when you create a .singleShot
timer you don’t (the static methods return None
) since the event is just pushed to the queue.
For threads, there are the usual rules – e.g. when you have two threads communicating like you do, be careful of deadlocks, where both threads end up waiting on data from each other.
Be careful about passing data between threads to avoid segmentation errors. You can send data between threads using signals, but signals send a reference to the same object (not a copy) – this is also true when communicating from the main thread. You’re using a Queue
which is a good solution, you can also create a new object by copying before send – this must be a deepcopy
, otherwise the internal elements will still be pointing to the same objects –
>> from copy import copy, deepcopy
>> my_dict = {'a': [1,2,3], 'b': [4,5,6]}
>> my_dict2 = copy(my_dict)
>>> my_dict is my_dict2
False
>>> my_dict['a'] is my_dict2['a']
True
>> my_dict3 = deepcopy(my_dict)
>>> my_dict['a'] is my_dict3['a']
False
Other than that, most of the specific PyQt5 weirdness is around performance and handling the output/signals returning data from a thread.
The goal of putting things in threads is often to keep the GUI responsive. To achieve this it is important that the worker threads don’t flood your main thread with “work” – particularly large amounts of result data that needs to be handled by the main thread.
Each signal coming back from the worker thread to the main thread is an interruption. For GUI responsiveness it’s better to have many short interruptions than few long interruptions. But that can go too far, e.g. if you’re triggering signals hundreds of times a second with a small task. In extreme cases you can flood the event queue and crash the app.
Remember that, if your slots are in Python –
- each of these interruptions requires a switch to Python (from the Qt event loop) which takes time
- no other events can be handled until your slot returns
- if your thread work isn’t releasing the GIL, this actually interrupts the thread too
Where the exact balance is depends on your application, so take some time to profile how often (and how long) your app is spending in slots.