PYQT5|Pyside2 background thread and signal mode multithreading to prevent interface jamming
Interface blocking problem
In the previous exercise, we developed an HTTP interface testing tool similar to Postman.
The specific code for sending the request message is as follows
def sendRequest(self): method = self.ui.boxMethod.currentText() url = self.ui.editUrl.text() payload = self.ui.editBody.toPlainText() # Get message header headers = {} # Some processing of message headers is omitted here req = requests.Request(method, url, headers=headers, data=payload ) prepared = req.prepare() self.pretty_print_request(prepared) s = requests.Session() try: # Send request and receive response message r = s.send(prepared) # Print out the response message self.pretty_print_response(r) except: self.ui.outputWindow.append( traceback.format_exc())
Here's a question:
We Click the send button If the server receives and processes HTTP messages slowly, it will take a long time for the send method in the following line of code to return.
r = s.send(prepared)
What problems does this lead to?
Assuming that the response message from the server is received after 10 seconds, the interface will dead 10 seconds.
During this period, you click the interface without any reaction.
Why?
reason
This is because our current code is executed in the main thread.
The last code
app.exec_()
In fact, it will let the main thread enter an endless loop, which continuously handles the events of user operation.
When we click the send button, Qt's core code will accept the click event and call the corresponding slot function to process it.
Because our code makes such a setting
# signal processing self.ui.buttonSend.clicked.connect(self.sendRequest)
Specifies that clicking the send button is handled by the sendRequest method.
If the sendRequest can receive the corresponding from the server soon, the sendRequest can be returned quickly.
After returning, the whole program enters app.exec again_ () receives various events and calls corresponding functions to process them. The interface will not freeze, because all the events of the operation interface can be handled in time.
However, if the sendRequest takes a long time to return, the whole program will stop at the following line of code during this time
r = s.send(prepared)
Naturally, there is no chance to deal with other user interface events. Of course, the program is dead.
Sub thread processing
A typical solution is to use multithreading.
About Python's multithreading, You can click here to refer to our tutorial
The modification code is as follows
def sendRequest(self): method = self.ui.boxMethod.currentText() url = self.ui.editUrl.text() payload = self.ui.editBody.toPlainText() # Get message header headers = {} # Some processing of message headers is omitted here req = requests.Request(method, url, headers=headers, data=payload ) prepared = req.prepare() self.pretty_print_request(prepared) s = requests.Session() # Create a new thread to execute the send method, # The server is slow and will only block in new threads # Does not affect the main thread thread = Thread(target = self.threadSend, args= (s, prepared) ) thread.start() # New thread entry function def threadSend(self,s,prepared): try: r = s.send(prepared) self.pretty_print_response(r) except: self.ui.outputWindow.append( traceback.format_exc())
In this way, by creating a new thread to execute the send method, no matter how slow the server response is, it will only block in the new thread
After the main thread starts a new thread, it will continue to execute the following code and return to the event loop processing of Qt. It can respond to the user's operation and will not freeze.
VIP practical class students please contact the teacher to obtain a complete code example.
The sub thread sends a signal to update the interface
Click here to learn the following while watching the video explanation
In the above example, we operate the interface in the sub thread, as shown in the following code
def threadSend(self,s,prepared): try: r = s.send(prepared) # Output content to the interface in a new thread self.pretty_print_response(r) except: # Output content to the interface in a new thread self.ui.outputWindow.append( traceback.format_exc())
Qt recommendations: Operate the interface only in the main thread .
The direct operation interface of another thread may lead to unexpected problems, such as incomplete output display and even program crash.
However, we do often need to update the interface in the child thread. For example, the sub thread is a crawler, and the crawled data is displayed on the interface.
What shall I do?
At this time, the recommended method is to use signals.
We have seen before that various Qt controls can send signals, such as being clicked, input, etc.
We can also customize the class. As long as this class inherits the QObject class, it can send various Qt signals defined by itself. The specific methods are as follows:
-
Customize a Qt QObject class, which encapsulates some custom Signal signals
How to encapsulate a custom Signal? Refer to the following example code.
A Signal is defined as a static attribute of this class, and the value is Signal instance object.
Can define Multiple Signal static property, which corresponds to that this type of object can emit varied Signal.
Note: the type specified by the initialization parameter of the Signal instance object is the parameter data type passed when the Signal object is sent. Because the underlying Qt is developed in C + +, the type must be specified.
-
Define the function executed by the main thread to process the Signal signal (through the connect method)
-
When a new thread needs an operation interface, it sends a signal through a custom object
Send a Signal through the emit method of the Signal object, and the parameters of the emit method pass the necessary data. The parameter type follows the type specified when defining Signal.
-
The Signal processing function of the main thread is triggered to execute, obtain the parameters in Signal, and perform necessary interface update operations
An example code is as follows
from PySide2.QtWidgets import QApplication, QTextBrowser from PySide2.QtUiTools import QUiLoader from threading import Thread from PySide2.QtCore import Signal,QObject # Custom signal source object types must inherit from QObject class MySignals(QObject): # Define a signal. The two parameter types are QTextBrowser and string # When calling the emit method to send a signal, the incoming parameter must be the parameter type specified here text_print = Signal(QTextBrowser,str) # Other kinds of signals can also be defined update_table = Signal(str) # instantiation global_ms = MySignals() class Stats: def __init__(self): self.ui = QUiLoader().load('main.ui') # Custom signal processing function global_ms.text_print.connect(self.printToGui) def printToGui(self,fb,text): fb.append(str(text)) fb.ensureCursorVisible() def task1(self): def threadFunc(): # Trigger the execution of the processing function in the main thread through Signal's emit # The quantity and type of the emit parameter and the defined Signal must be consistent global_ms.text_print.emit(self.ui.infoBox1, 'Output content') thread = Thread(target = threadFunc ) thread.start() def task2(self): def threadFunc(): global_ms.text_print.emit(self.ui.infoBox2, 'Output content') thread = Thread(target=threadFunc) thread.start()