Easy way to call a function on the main thread in Qt (PySide2) - python-3.x

Is there any easy way to call a function or method in the main thread from any other thread or QThread?
I heard that Slots and Signals can be used as a proxy between a thread and the main thread but it feels like too much work to create such a proxy for every single time I want to transfer data to my main thread.
(My answer describes a very universal way to acclompish this so I won't be providing a "minimal" example here, you can look at the answer.)

Qt has a function called invokeMethod (https://doc.qt.io/qt-5/qmetaobject.html#invokeMethod) that can be used to invoke a method on the main thread by using the Qt.QueuedConnection connection type.
In PySide however this won't work if you want to call a function with arguments!
Solution:
So to allow a similar and even easier functionality with PySide, I have written this class which can be used every time you want to run a function or method on the main thread:
from typing import Callable
from PySide2.QtCore import QObject, Signal, Slot
from PySide2.QtGui import QGuiApplication
class InvokeMethod(QObject):
def __init__(self, method: Callable):
"""
Invokes a method on the main thread. Taking care of garbage collection "bugs".
"""
super().__init__()
main_thread = QGuiApplication.instance().thread()
self.moveToThread(main_thread)
self.setParent(QGuiApplication.instance())
self.method = method
self.called.connect(self.execute)
self.called.emit()
called = Signal()
#Slot()
def execute(self):
self.method()
# trigger garbage collector
self.setParent(None)
This will internally create a Signal and a Slot without any parameters. The Slot however will be called on the main thread as it has been connected using Qt.AutoConnection (the default) and moved to the main thread with moveToThread(...).
To make sure no function arguments get lost due to the garbage collector, the parent of the class is temporarily set to the QGuiApplication instance (you might need to change this if you don't rely on the QGuiApplication. Any QObject will be fine as the parent).
Here is an example on how to use this class:
InvokeMethod(lambda: print("hello"))

Related

What is the difference between threading function and normal function. Does it differ in memory allocation or execution?

Assume I have a function Handle client
def Handle_Client():
print('Hello StackOverFlow user')
one way to call function handel client using threads
Client_Thread=Thread(target=Handle_Client,args=())
Client_Thread.start()
second way
Client_Thread=Handle_Client()
what is the diff in terms of memory ,execution or is it the same?
In terms of execution,
The first way you mentioned
Client_Thread=Thread(target=Handle_Client,args=(1,))
Client_Thread.start()
When you create a Thread, you pass it a function and a list containing the arguments to that function. In this case, you’re telling the Thread to run Handle_Client() and to pass it 1 as an argument.
The second way you mentioned is done by creating a class. For instance
class Handle_Client(threading.Thread):
def __init__(self, i):
threading.Thread.__init__(self)
self.h = i
def run(self):
print(“ Value send“, self.h)
Client_Thread = Handle_Client(1)
Client_Thread.start()
Here, New class Handle_Client inherits the Python threading.Thread class.
__init__(self [,args]): Override the constructor.
run(): This is the section where you can put your logic part.
start(): The start() method starts a Python thread.
The Handle_Client class overrides the constructor, so the base class constructor (Thread.__init__()) must be invoked.
In terms of memory,
In the first method, you run a function as a separate thread, whereas in the second method you have to create a class, but get more functionality.
That means it would take additional memory since you get more functionality.
I am not a 100% sure about it but that's what I understood.

PyQt5 - QMovie in MainWindow doesn't play even when using QThread

I am trying to display a loading gif on my PyQt5 QMainWindow while an intensive process is running. Rather than playing normally, the QMovie pauses. As far as I can tell, the event loop shouldn't be blocked as the intensive process is in its own QObject passed to its own QThread. Relevant code below:
QMainWindow:
class EclipseQa(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.initUI()
def initUI(self):
...
self.loadingMovie = QMovie("./loading.gif")
self.loadingMovie.setScaledSize(QSize(149, 43))
self.statusLbl = QLabel(self)
self.statusLbl.setMovie(self.loadingMovie)
self.grid.addWidget(self.statusLbl, 6, 2, 2, 2, alignment=Qt.AlignCenter)
self.statusLbl.hide()
...
def startLoadingGif(self):
self.statusLbl.show()
self.loadingMovie.start()
def stopLoadingGif(self):
self.loadingMovie.stop()
self.statusLbl.hide()
def maskDose(self):
self.startLoadingGif()
# Set up thread and associated worker object
self.thread = QThread()
self.worker = DcmReadWorker()
self.worker.moveToThread(self.thread)
self.worker.finished.connect(self.thread.quit)
self.worker.updateRd.connect(self.updateRd)
self.worker.updateRs.connect(self.updateRs)
self.worker.updateStructures.connect(self.updateStructures)
self.worker.clearRd.connect(self.clearRd)
self.worker.clearRs.connect(self.clearRs)
self.thread.started.connect(lambda: self.worker.dcmRead(caption, fname[0]))
self.thread.finished.connect(self.stopLoadingGif)
self.maskThread.start()
def showDoneDialog(self):
...
self.stopLoadingGif()
...
Worker class:
class DoseMaskWorker(QObject):
clearRd = pyqtSignal()
clearRs = pyqtSignal()
finished = pyqtSignal()
startLoadingGif = pyqtSignal()
stopLoadingGif = pyqtSignal()
updateMaskedRd = pyqtSignal(str)
def __init__(self, parent=None):
QObject.__init__(self, parent)
#pyqtSlot(name="maskDose")
def maskDose(self, rd, rdName, rdId, rs, maskingStructure_dict):
...
self.updateMaskedRd.emit(maskedRdName)
self.finished.emit()
For brevity, '...' indicates code that I figured is probably not relevant.
Your use of lambda to call the slot when the threads started signal is emitted is likely causing it to execute in the main thread. There are a couple of things you need to do to fix this.
Firstly, your use of pyqtSlot does not contain the types of the arguments to the maskDose method. You need to update it so that it does. Presumably you also need to do this for the dcmRead method which you call from the lambda but haven't included in your code. See the documentation for more details.
In order to remove the use of the lambda, you need to define a new signal and a new slot within the EclipseQa class. The new signal should be defined such that the required number of parameters for the dcmRead method are emitted, and the types correctly specified (documentation for this is also in the link above). This signal should be connected to the workers dcmRead slot (make sure to do it after the worker object has been moved to the thread or else you might run into this bug!). The slot should take no arguments, and be connected to the threads started signal. The code in the slot should simply emit your new signal with the appropriate arguments to be passed to dcmRead (e.g. like self.my_new_signal.emit(param1, param2)).
Note: You can check what thread any code is running in using the Python threading module (even when using QThreads) by printing threading.current_thread().name from the location you wish to check.
Note 2: If your thread is CPU bound rather than IO bound, you may still experience performance issues because of the Python GIL which only allows a single thread to execute at any one time (it will swap between the threads regularly though so code in both threads should run, just maybe not at the performance you expect). QThreads (which are implemented in C++ and are theoretically able to release the GIL) do not help with this because they are running your Python code and so the GIL is still held.

Terminating a QThread that wraps a function (i.e. can't poll a "wants_to_end" flag)

I'm trying to create a thread for a GUI that wraps a long-running function. My problem is thus phrased in terms of PyQt and QThreads, but I imagine the same concept could apply to standard python threads too, and would appreciate any suggestions generally.
Typically, to allow a thread to be exited while running, I understand that including a "wants_to_end" flag that is periodically checked within the thread is a good practice - e.g.:
Pseudocode (in my thread):
def run(self):
i = 0
while (not self.wants_to_end) and (i < 100):
function_step(i) # where this is some long-running function that includes many streps
i += 1
However, as my GUI is to wrap a pre-written long-running function, I cannot simply insert such a "wants_to_end" flag poll into the long running code.
Is there another way to forcibly terminate my worker thread from my main GUI (i.e. enabling me to include a button in the GUI to stop the processing)?
My simple example case is:
class Worker(QObject):
finished = pyqtSignal()
def __init__(self, parent=None, **kwargs):
super().__init__(parent)
self.kwargs = kwargs
#pyqtSlot()
def run(self):
result = SomeLongComplicatedProcess(**self.kwargs)
self.finished.emit(result)
with usage within my MainWindow GUI:
self.thread = QThread()
self.worker = Worker(arg_a=1, arg_b=2)
self.worker.finished.connect(self.doSomethingInGUI)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.thread.start()
If the long-running function blocks, the only way to forcibly stop the thread is via its terminate() method (it may also be necessary to call wait() as well). However, there is no guarantee that this will always work, and the docs also state the following:
Warning: This function is dangerous and its use is discouraged. The
thread can be terminated at any point in its code path. Threads can be
terminated while modifying data. There is no chance for the thread to
clean up after itself, unlock any held mutexes, etc. In short, use
this function only if absolutely necessary.
A much cleaner solution is to use a separate process, rather than a separate thread. In python, this could mean using the multiprocessing module. But if you aren't familiar with that, it might be simpler to run the function as a script via QProcess (which provides signals that should allow easier integration with your GUI). You can then simply kill() the worker process whenever necessary. However, if that solution is somehow unsatisfactory, there are many other IPC approaches that might better suit your requirements.

Can you create a separate QThread class and only call a specific function from it?

I have tried to read as much as I can about PyQt4's QThread and the idea of the worker thread. My question is, instead of building a QThread class to run everything in it from the def run(self): by the blahblah.start() command is there a way to create that individual thread class that has,say, 4 functions and you only call function 2 and then close that thread right after?
Subclassing QThread is a practice that is in general discouraged although often used. [see comment below]
In my opinion, this is a good example of how to use a thread in pyqt. You would create a Worker and a Thread, where the Worker is some general class of type QObject and the Thread is a QThread which you do not subclass. You'd then move the Worker to the Threat and start it.
self.worker = WorkerObject()
self.worker_thread = QtCore.QThread()
self.worker.moveToThread(self.worker_thread)
self.worker_thread.start()
Inside the Worker you can basically do whatever you want, it can have arbitrary many methods and so on.
The one big thing to keep in mind is that the Worker needs to be separate from the main loop. So the methods should not return anything that is used in the main loop (better not return anything at all) and the Worker's results should be collected using signals and slots.
self.button_start.clicked.connect(self.worker.startWork)
self.button_do_something_else.clicked.connect(self.worker.function2)
self.worker.signalStatus.connect(self.updateStatus)
Also make sure not to use any PyQt/GUI objects inside the worker, as this would also build a bridge between Worker and main loop through PyQt itself.

Can I use waitForReadyRead in a QThread that belongs to the mainthread?

I have a QThread that contains a QUDPsocket (socket is member not local to QThread::run(), maybe I should change that from what I am reading). This QThread is instantiated in my QMainWindow class ie the GUI thread(I am not calling move to thread). Is it still safe to use waitForReadyRead or do I absolutly need to instantiate the QThread in main.cpp or call moveToThread() for it to be thread safe. I am getting intermittent double free exception inside the call to waitForReadyRead in the present way of doing it(sometimes I dont get it for days sometimes after 3 minutes).
Have a look at the Qt documentation for QUdpSocket. There is a note there explaining the class is reentrant. Also from the Qt documentation:
...a class is said to be reentrant if its member functions can be called safely from multiple threads, as long as each thread uses a different instance of the class.
Thus, to answer your question, it does not really matter what the parent of the QThread is, as long as you make sure that the QUdpSocket instance you are using is instantiated within the context of the thread you are using it in.

Resources