Python中使用PyQt5的QThread分离UI和逻辑,为什么UI仍然会卡住?

class Ux(QMainWindow, window.Ui_MainWindow):
    @pyqtSlot()
    def do_stop_search(self):
        pass
@pyqtSlot(dict)
def do_append_result(self, item):
    pass

def on_btn_search_clicked(self):
    self.search = SearchThread(self, text)
    self.search.run()

class SearchThread(QThread): to_append_result = pyqtSignal([dict]) to_stop_search = pyqtSignal()

def __init__(self, obj, key_word):
    super().__init__()
    self.site = obj.site         # obj.site is a class
    self.key = key_word
    self.to_append_result.connect(obj.do_append_result)
    self.to_stop_search.connect(obj.do_stop_search)

def run(self):
    search = self.site(self.to_append_result)
    search.search(self.key)     # do a lot of things
    self.to_stop_search.emit()

请问这个实现哪里出了问题?


Python中使用PyQt5的QThread分离UI和逻辑,为什么UI仍然会卡住?

11 回复

没用过 pyqt,但是在用原生 qt,qt 起线程不是 start 吗? 你怎么直接调用 run。。。


你遇到的问题很典型。很多人以为用了QThread就万事大吉,但其实用错了方式。核心问题在于:你很可能在子线程里直接操作了UI组件

在PyQt5里,所有UI操作都必须在主线程(GUI线程)里执行。如果你在QThread子线程里直接调用self.some_label.setText()这类方法,虽然程序可能不会立刻崩溃,但会导致UI响应卡顿,因为Qt的事件循环被阻塞了。

下面是一个正确的示例,展示如何通过信号槽机制安全地在线程和UI之间通信:

import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal, QObject
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QLabel

class Worker(QObject):
    # 定义信号,用于将数据发送回主线程
    result_ready = pyqtSignal(str)
    finished = pyqtSignal()

    def run_long_task(self):
        """模拟一个耗时的任务"""
        for i in range(5):
            time.sleep(1)  # 模拟耗时操作
            # 通过信号发送进度信息,而不是直接操作UI
            self.result_ready.emit(f"Processing... Step {i+1}/5")
        self.result_ready.emit("Task completed!")
        self.finished.emit()

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

    def init_ui(self):
        self.label = QLabel("Ready")
        self.button = QPushButton("Start Task")
        self.button.clicked.connect(self.start_task)

        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.button)
        self.setLayout(layout)

    def setup_thread(self):
        # 创建线程和工作者对象
        self.thread = QThread()
        self.worker = Worker()
        self.worker.moveToThread(self.thread)  # 关键步骤:将worker移到线程中

        # 连接信号和槽
        self.worker.result_ready.connect(self.update_label)
        self.worker.finished.connect(self.on_task_finished)
        self.thread.started.connect(self.worker.run_long_task)

        # 线程结束时清理
        self.thread.finished.connect(self.thread.deleteLater)

    def start_task(self):
        self.button.setEnabled(False)
        self.label.setText("Task started...")
        self.thread.start()  # 启动线程,这会触发thread.started信号,进而调用worker.run_long_task

    def update_label(self, text):
        # 这个槽函数在主线程中被调用,所以可以安全更新UI
        self.label.setText(text)

    def on_task_finished(self):
        self.button.setEnabled(True)
        self.thread.quit()  # 停止线程的事件循环
        # 注意:不要直接调用self.thread.wait(),这可能会阻塞UI

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

关键点总结:

  1. 不要继承QThread:推荐使用QObject作为工作类(Worker),然后用moveToThread()将它移到一个QThread实例中。这样更符合Qt的设计模式。
  2. 所有UI交互通过信号槽:耗时的逻辑在Worker的槽函数里运行(比如run_long_task)。任何需要更新UI的信息(如进度、结果)都必须通过pyqtSignal发送信号,连接到主线程的槽函数来执行UI更新。
  3. Worker里不要有UI组件引用:Worker对象应该只负责逻辑计算,完全不知道UI的存在。它只负责发出信号。

你UI卡住的原因99%是违反了上面第2条。检查你的代码,确保在线程函数里没有任何一行直接调用了类似setTextsetValueaddItem等UI组件的方法。

一句话建议: 确保耗时逻辑在Worker对象里运行,并通过信号槽与主线程通信,切勿在线程中直接操作UI组件。

因为你在 ui 线程里调用的 run 函数,创建的新线程没用

楼主你要补一下基础知识,你这直接调用 run 怎么能行呢,跟不用线程还有啥区别,用 start 启动线程

擦,手抖了。。。。多谢

手抖了。。。。多谢

手抖了。。。。多谢

strat() 正解,以前我也写过 pyqt,现在感觉真麻烦,还不如写成 web

web 爬虫的话,是用后台的 python 做,还是直接 javascript 爬?

当然是 python 啊,你看一下我博客的工具页面,前面的三个工具都是利用的 Python 的爬虫完成的后台,然后把结果传递到前台展现,ajax 传递数据就行了,这是小爬虫。如果是耗时间比较长的爬虫可以设置定时任务让 python 爬,然后把结果存起来,前台要调用的时候去读取结果就行了,这是我理解的方式

忘了发博客工具页面的链接了 http://www.tendcode.com/tool/,还有我以前写的 pyqt 的工具,是多线程的任务,https://github.com/Hopetree/TMTools

回到顶部