1. Intro
I'm working with PyQt5 in Python 3.7 on a multithreaded application, for which I rely on the QThread.
Now suppose I have a class derived from QObject. Within that class, I define a function annotated with @pyqtSlot:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import threading
...
class Worker(QObject):
def __init__(self):
super().__init__()
return
@pyqtSlot()
def some_function(self):
...
return
In some other code, I instantiate Worker() and move it to a new thread, like so:
my_thread = QThread()
my_worker = Worker()
my_worker.moveToThread(my_thread)
my_thread.start()
QTimer.singleShot(100, my_worker.some_function)
return
Normally, some_function() should now run in my_thread. That's because:
- I've pushed the
Worker()object tomy_thread. - When commanding
my_threadto start, I've actually given birth to a new Qt event-loop in that thread. Themy_workerobject lives in this event-loop. All its slots can receive an event, which gets executed in this event-loop. some_function()is properly annotated to be a@pyqtSlot(). The single-shot-timer hooks onto this slot and fires an event. Thanks to the Qt-event-loop inmy_thread, the slot effectively executes its code inmy_thread.
2. My question
My question is about nested functions (also called 'inner functions'). Consider this:
class Worker(QObject):
def __init__(self):
super().__init__()
return
def some_function(self):
...
@pyqtSlot()
def some_inner_function():
...
return
return
As you can see, some_inner_function() is annotated as @pyqtSlot. Will its code also run in the thread the Worker()-object lives in?
3. Sidenote: how to hook to the inner function
You might wonder how I could hook something to the inner function. Well, consider the following:
class Worker(QObject):
def __init__(self):
super().__init__()
return
def some_function(self):
@pyqtSlot()
def some_inner_function():
# Will this code run in `my_thread`?
...
return
# some_function() will run in the main thread if
# it is called directly from the main thread.
QTimer.singleShot(100, some_inner_function)
return
If you call some_function() directly from the main thread, it will (unfortunately) run in the main thread. Without properly using the signal-slot mechanism, you won't switch threads.
The single-shot-timer inside some_function() hooks onto some_inner_function() and fires. Will the inner function execute in my_thread (supposing that the Worker()-object was assigned to my_thread)?
In Qt there are the following rules about what:
If you call a callable directly it will run on the thread where it was called.
If a callable is invoked indirectly (through qt signals,
QTimer::singleShot()orQMetaObject::invokeMethod()) it will be executed in the context to which it belongs. And the context refers to the QObject.If the callable does not belong to a context this will be executed in the thread where it was indirectly called.
The internal functions do not belong to a context, so even if it is called directly or indirectly, it will be executed in the thread where it was invoked.
Based on the above, let's analyze several cases as an exercise to verify the previous rules:
Example 1
Output:
In this case rule 1 is fulfilled because all callables are called directly.
Example 2
Output:
In this some function is called directly in the main thread so it will be executed in that thread, and since the some_inner_function is called by some_function then it will also be executed in that thread.
Example3:
Output:
In this case some_function is invoked indirectly and belongs to the Worker context so it will be executed on a secondary thread so some_inner_function will be executed on the secondary thread.
In conclusion
some_inner_functionwill run on the same thread assome_functionwas executed, even call it directly or indirectly since it has no context.