简体   繁体   English

PyQt5 QThread不起作用,GUI仍然冻结

[英]PyQt5 QThread not working, gui still freezing

I have this code (if you have pyqt5, you should be able to run it yourself): 我有以下代码(如果您有pyqt5,则应该可以自己运行):

import sys
import time

from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot


class Worker(QObject):
    def __init__(self):
        super().__init__()
        self.thread = None


class Tab(QObject):
    def __init__(self, _main):
        super().__init__()
        self._main = _main


class WorkerOne(Worker):
    finished = pyqtSignal()

    def __init__(self):
        super().__init__()

    @pyqtSlot(str)
    def print_name(self, name):
        for _ in range(100):
            print("Hello there, {0}!".format(name))
            time.sleep(1)

        self.finished.emit()
        self.thread.quit()


class SomeTabController(Tab):
    def __init__(self, _main):
        super().__init__(_main)
        self.threads = {}

        self._main.button_start_thread.clicked.connect(self.start_thread)

        # Workers
        self.worker1 = WorkerOne()
        #self.worker2 = WorkerTwo()
        #self.worker3 = WorkerThree()
        #self.worker4 = WorkerFour()

    def _threaded_call(self, worker, fn, *args, signals=None, slots=None):
        thread = QThread()
        thread.setObjectName('thread_' + worker.__class__.__name__)

        # store because garbage collection
        self.threads[worker] = thread

        # give worker thread so it can be quit()
        worker.thread = thread

        # objects stay on threads after thread.quit()
        # need to move back to main thread to recycle the same Worker.
        # Error is thrown about Worker having thread (0x0) if you don't do this
        worker.moveToThread(QThread.currentThread())

        # move to newly created thread
        worker.moveToThread(thread)

        # Can now apply cross-thread signals/slots

        #worker.signals.connect(self.slots)
        if signals:
            for signal, slot in signals.items():
                try:
                    signal.disconnect()
                except TypeError:  # Signal has no slots to disconnect
                    pass
                signal.connect(slot)

        #self.signals.connect(worker.slots)
        if slots:
            for slot, signal in slots.items():
                try:
                    signal.disconnect()
                except TypeError:  # Signal has no slots to disconnect
                    pass
                signal.connect(slot)

        thread.started.connect(lambda: fn(*args)) # fn needs to be slot
        thread.start()

    @pyqtSlot()
    def _receive_signal(self):
        print("Signal received.")

    @pyqtSlot(bool)
    def start_thread(self):
        name = "Bob"
        signals = {self.worker1.finished: self._receive_signal}
        self._threaded_call(self.worker1, self.worker1.print_name, name,
                            signals=signals)


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Thread Example")
        form_layout = QVBoxLayout()
        self.setLayout(form_layout)
        self.resize(400, 400)

        self.button_start_thread = QPushButton()
        self.button_start_thread.setText("Start thread.")
        form_layout.addWidget(self.button_start_thread)

        self.controller = SomeTabController(self)


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

    _main = MainWindow()
    _main.show()

    sys.exit(app.exec_())

However WorkerOne still blocks my GUI thread and the window is non-responsive when WorkerOne.print_name is running. 但是, WorkerOne仍会阻止我的GUI线程,并且在运行WorkerOne.print_name时,窗口无响应。

I have been researching a lot about QThreads recently and I am not sure why this isn't working based on the research I've done. 我最近对QThreads进行了很多研究,但根据我所做的研究,我不确定为什么它不起作用。

What gives? 是什么赋予了?

The problem is caused by the connection with the lambda method since this lambda is not part of the Worker so it does not run on the new thread. 该问题是由与lambda方法的连接引起的,因为此lambda不是Worker的一部分,因此它无法在新线程上运行。 The solution is to use functools.partial : 解决方案是使用functools.partial

from functools import partial
...
thread.started.connect(partial(fn, *args))

Complete Code: 完整的代码:

import sys
import time

from functools import partial

from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot


class Worker(QObject):
    def __init__(self):
        super().__init__()
        self.thread = None


class Tab(QObject):
    def __init__(self, _main):
        super().__init__()
        self._main = _main


class WorkerOne(Worker):
    finished = pyqtSignal()

    def __init__(self):
        super().__init__()

    @pyqtSlot(str)
    def print_name(self, name):
        for _ in range(100):
            print("Hello there, {0}!".format(name))
            time.sleep(1)

        self.finished.emit()
        self.thread.quit()


class SomeTabController(Tab):
    def __init__(self, _main):
        super().__init__(_main)
        self.threads = {}

        self._main.button_start_thread.clicked.connect(self.start_thread)

        # Workers
        self.worker1 = WorkerOne()
        #self.worker2 = WorkerTwo()
        #self.worker3 = WorkerThree()
        #self.worker4 = WorkerFour()

    def _threaded_call(self, worker, fn, *args, signals=None, slots=None):
        thread = QThread()
        thread.setObjectName('thread_' + worker.__class__.__name__)

        # store because garbage collection
        self.threads[worker] = thread

        # give worker thread so it can be quit()
        worker.thread = thread

        # objects stay on threads after thread.quit()
        # need to move back to main thread to recycle the same Worker.
        # Error is thrown about Worker having thread (0x0) if you don't do this
        worker.moveToThread(QThread.currentThread())

        # move to newly created thread
        worker.moveToThread(thread)

        # Can now apply cross-thread signals/slots

        #worker.signals.connect(self.slots)
        if signals:
            for signal, slot in signals.items():
                try:
                    signal.disconnect()
                except TypeError:  # Signal has no slots to disconnect
                    pass
                signal.connect(slot)

        #self.signals.connect(worker.slots)
        if slots:
            for slot, signal in slots.items():
                try:
                    signal.disconnect()
                except TypeError:  # Signal has no slots to disconnect
                    pass
                signal.connect(slot)

        thread.started.connect(partial(fn, *args)) # fn needs to be slot
        thread.start()

    @pyqtSlot()
    def _receive_signal(self):
        print("Signal received.")

    @pyqtSlot(bool)
    def start_thread(self):
        name = "Bob"
        signals = {self.worker1.finished: self._receive_signal}
        self._threaded_call(self.worker1, self.worker1.print_name, name,
                            signals=signals)


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Thread Example")
        form_layout = QVBoxLayout()
        self.setLayout(form_layout)
        self.resize(400, 400)

        self.button_start_thread = QPushButton()
        self.button_start_thread.setText("Start thread.")
        form_layout.addWidget(self.button_start_thread)

        self.controller = SomeTabController(self)


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

    _main = MainWindow()
    _main.show()

    sys.exit(app.exec_())

To avoid blocking the gui for a slideshow function showing images i run the following simple code. 为了避免阻塞显示图像的幻灯片功能的gui,我运行以下简单代码。

A background Thread Class to wait one second, then signal to the gui waiting is finished. 后台线程类等待一秒钟,然后通知gui等待完成。

class Waiter(QThread):
    result = pyqtSignal(object)

    def __init__(self):
        QtCore.QThread.__init__(self)

    def run(self):
        while self.isRunning:
            self.sleep(1)
            self.result.emit("waited for 1s")

Then in main window connect the slideshow start and stop button to start and stop methods of main app and connect the nextImage function to the signal the Waiter Thread emits. 然后在主窗口中,连接幻灯片开始和停止按钮以启动和停止主应用程序的方法,并将nextImage函数连接到Waiter Thread发出的信号。

    self.actionstopSlideShow.triggered.connect(self.stopSlideShow)
    self.actionslideShowStart.triggered.connect(self.startSlideShow)
    self.waitthread = Waiter()
    self.waitthread.result.connect(self.nextImage)

Then two methods of main app allow to start and stop the time ticker 然后,主应用程序的两种方法允许启动和停止时间行情记录器

def startSlideShow(self):
    """Start background thread that waits one second,
    on wait result trigger next image
    use thread otherwise gui freezes and stop button cannot be pressed
    """
    self.waitthread.start()

def stopSlideShow(self):
    self.waitthread.terminate()
    self.waitthread.wait()

Up to now i have no problems subclassing from QThread in pyqt5, gui changes are all handled inside the main (gui) thread. 到目前为止,我在pyqt5中从QThread继承子类没有问题,gui更改全部在主(gui)线程内处理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM