Pyqt5's QThead thread object implements thread start, pause, resume, and end

foreword

I recently learned Pyqt5 and studied the QThead thread object. Because there is little information on this aspect on the Internet, after studying, I will record my understanding as follows.

Disclaimer: The understanding is based on the analysis of blogs of other bigwigs. Drinking water does not forget the well diggers. The blogs of bigwigs are as follows:

https://huaweicloud.csdn.net/638071cedacf622b8df8844e.html

https://blog.csdn.net/tcy23456/article/details/107904530

https://blog.csdn.net/jeekmary/article/details/88739092

https://www.cnblogs.com/mosewumo/p/12486228.html

1. Basic use of QThead threads

The use of threads is very simple. We only need to write a class and inherit the QThead class. We need to pay attention to the following two points:

1. In the initialization method of the custom thread class __init__, the method of the parent class must be called __init__, otherwise an error will be reported.
2. Override run()the method of the parent class and execute the business logic here.
3. After declaring the object of the custom thread class, calling start()the method of the object will start the thread and enter the method of the thread . After the method run()is executed , the thread ends.run()

Examples are as follows:

import sys
import time

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class MyThread(QThread):
    def __init__(self):
        super(MyThread, self).__init__()

    def run(self):
        for i in range(10):
            print(i)
            time.sleep(1)

class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("PyQt5多线程学习")
        self.resize(400, 300)

        # 开启线程按钮
        btn_start = QPushButton(self)
        btn_start.setText("开始")
        btn_start.clicked.connect(self.start)


    # 开启线程
    def start(self):
        self.my_thread = MyThread()
        self.my_thread.start()

if __name__ == '__main__':
    app = QApplication(sys.argv)

    w = MyWindow()
    w.show()

    sys.exit(app.exec_())

Customize the thread class MyThread(), declare the object my_thread, call start()the method, start the thread, execute run()the content of the method in the thread, and output a number every second.

2. Thread pause and resume

Introduce how to realize the recovery and suspension of QThead thread class.

We achieve these two functions QWaitCondition()with these two classes.QMutex()

1. The function of QMutex() is to lock and unlock the thread. Before the thread is suspended, the thread is locked to prevent the data state from changing. After the thread is awakened, it is unlocked. The thread locking method is: QMutex().lock(), and the thread unlocking method is: QMutex().unlock().
We only need to create a QMutex() object in the thread class, and call the corresponding method to lock and unlock the thread.

2. The function of QWaitCondition() is to suspend and resume the thread. The method of suspending the thread is: QWaitCondition().wait(QMutex()), we need to pass in the locked QMutex() object as a parameter. The method of thread recovery is: QWaitCondition().wakeAll, the thread that has been wait() can be recovered. QWaitCondition() is used for multi-thread synchronization. A thread calls QWaitCondition.wait() to block and wait until another thread calls QWaitCondition.wake() to wake up the thread, and the thread continues to execute.

Let's give an example of a progress bar to get familiar with these functions:

from PyQt5.QtCore import QThread, QWaitCondition, QMutex, pyqtSignal, Qt
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QProgressBar, QApplication


class Thread(QThread):
    valueChange = pyqtSignal(int)

    def __init__(self, *args, **kwargs):
        super(Thread, self).__init__(*args, **kwargs)
        self._isPause = False  # 是否暂停
        self._value = 0
        self.cond = QWaitCondition()
        self.mutex = QMutex()

    # 暂停(挂起)
    def pause(self):
        # 状态改为暂停
        self._isPause = True

    # 恢复
    def resume(self):
        # 状态改为恢复
        self._isPause = False
        # 调用QWaitCondition.wake()唤醒暂停的线程
        self.cond.wakeAll()

    def run(self):
        # 开启线程,一直循环重复下去,监听状态
        while 1:
            # 给线程上锁,防止线程挂起的时候,线程中的数据状态发生改变
            self.mutex.lock()

            # 如果是暂停状态,则阻塞等待
            if self._isPause:
                self.cond.wait(self.mutex)

            # 进度条不能超过100
            if self._value > 100:
                self._value = 0

            # 每隔0.1秒,进度条+1
            self._value += 1

            # 发送发信号,改变进度条的数据
            self.valueChange.emit(self._value)

            self.msleep(100)

            # 线程恢复,解锁
            self.mutex.unlock()


class Window(QWidget):

    def __init__(self, *args, **kwargs):
        super(Window, self).__init__(*args, **kwargs)
        layout = QVBoxLayout(self)
        self.progressBar = QProgressBar(self)
        layout.addWidget(self.progressBar)
        layout.addWidget(QPushButton('休眠', self, clicked=self.doWait))
        layout.addWidget(QPushButton('唤醒', self, clicked=self.doWake))

        self.t = Thread(self)
        # 信号可以连接自定义的函数,也可以连接默认的函数
        self.t.valueChange.connect(self.progressBar.setValue)
        self.t.start()

    # 暂停
    def doWait(self):
        self.t.pause()

    # 恢复
    def doWake(self):
        self.t.resume()


if __name__ == '__main__':
    import sys
    # import cgitb

    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    # cgitb.enable(format='text')

    app = QApplication(sys.argv)
    w = Window()
    w.show()
    sys.exit(app.exec_())

I have added comments in the program, it is not difficult to understand.

The screenshot of the program running is as follows:
insert image description here

Here are a few points to note:

1. Why is the run method while 1used for an infinite loop?

2. Why add one _isPauseto identify whether the thread is suspended?

3. Why do you need to lock and unlock each cycle?

Being able to answer the above three questions shows that you can analyze them! ! ! Good good good good! ! !

3. The thread exits

Similar to QT, I believe that if you understand the above, it is also easy to read the following blog:
https://blog.csdn.net/qq_44365088/article/details/119087454

I am afraid that some comrades will not be able to calm down and look in, so I will explain the simplest one here by the way:

1. Directly let the thread object call terminate()the method to end the thread. Terminates the execution of the thread. A thread may or may not terminate immediately, depending on the operating system's scheduling policy. Please use after terminate()QThread().wait()

Warning: This function is dangerous and its use is discouraged. A thread can terminate at any point in its code path. Threads can terminate while modifying data. The thread doesn't get a chance to clean up itself, unlock any held mutexes, etc. In short, use this function only when absolutely necessary. The case is as follows, following the above thread creation code, just add a button to end the thread:

import sys
import time

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class MyThread(QThread):
    def __init__(self):
        super(MyThread, self).__init__()

    def run(self):
        for i in range(10):
            print(i)
            time.sleep(1)

class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("PyQt5多线程学习")
        self.resize(400, 300)

        # 开启线程按钮
        btn_start = QPushButton(self)
        btn_start.setText("开始")
        btn_start.clicked.connect(self.start)

        # 结束线程按钮
        btn_end = QPushButton(self)
        btn_end.setText("结束")
        btn_end.move(100, 0)
        btn_end.clicked.connect(self.end)

    # 结束线程
    def end(self):
        self.my_thread.terminate()
        # wait函数是个阻塞的接口,意思是线程必须真的退出了,才会执行wait之后的语句,否则将会一直阻塞在这里,如果在界面上使用,需要保证线程中代码的合理性。
        self.my_thread.wait()
        print('结束线程')

    # 开启线程
    def start(self):
        self.my_thread = MyThread()
        self.my_thread.start()

if __name__ == '__main__':
    app = QApplication(sys.argv)

    w = MyWindow()
    w.show()

    sys.exit(app.exec_())

Summarize

Thank you again for your blogs! !

eggs

I am here to answer the above three questions:

1、为何run方法中要用while 1来进行无限循环?

因为进度条是每隔0.1秒就会改变一次,每次循环都会+1,这是其一。
通过while 1 来实现监听_isPause的状态,这是其二。
2、为何要添加一个_isPause来标识线程是否暂停?

线程只能在自己内部调用wait方法将自己暂停,所以需要通过一个标识来判断此时是否应该将自己暂停,并且不断对标识进行监听。
若暂停与唤醒都能在其他线程实现,则不需要标识。
3、为何每次循环都要加锁与解锁各一次?

因为无法确定此次循环,是否需要暂停,所以每次循环都加锁与解锁各一次,更加安全。
你可能会说,在:
            if self._isPause:
                self.cond.wait(self.mutex)
中不就是需要暂停吗,在这个if中加锁不就行了吗?非也!若在此加锁,那么在那里解锁呢?每次循环都解锁一次吗?没有加锁的循环,
如果解锁的话便会报错。所以每次循环都加锁与解锁一次是最理想的。

Guess you like

Origin blog.csdn.net/qq_47188967/article/details/131323827