PyQt5应用启用多线程仍卡顿,求排查解决方法
PyQt5多线程任务仍导致界面卡顿,无法追踪进度
问题描述
我开发了一款PyQt5应用,核心功能是筛选用户名前缀为用户输入内容、序号范围0-101内,粉丝数≤20的公开Instagram账号,结果保存为指定文件名的CSV文件。原本单线程运行时界面全程卡顿,改用QThreadPool实现多线程后,界面依旧卡顿,无法追踪任务进度。在多台16GB内存的Win10设备测试均出现此问题,作为多线程新手,求排查思路或解决方案。
原代码
from PyQt5 import QtCore, QtGui, QtWidgets import html.parser import requests as rq from bs4 import BeautifulSoup as bs from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.QtCore import * import re from instagrapi import Client import time import csv import threading class WorkerSignals(QObject): finished = pyqtSignal() error = pyqtSignal(tuple) result = pyqtSignal(object) progress = pyqtSignal(int, str) class Worker(QRunnable): def __init__(self, fn, *args, **kwargs): super(Worker, self).__init__() # Store constructor arguments (re-used for processing) self.fn = fn self.args = args self.kwargs = kwargs self.signals = WorkerSignals() # Add the callback to our kwargs self.kwargs['progress_callback'] = self.signals.progress @pyqtSlot() def run(self): # Retrieve args/kwargs here; and fire processing using them try: result = self.fn(*self.args, **self.kwargs) except Exception as ea: print(ea) exctype, value = sys.exc_info()[:2] else: self.signals.result.emit(result) # Return the result of the processing finally: self.signals.finished.emit() # Done class Tasks(QObject): result: object def __init__(self): super(Tasks, self).__init__() self.pool = QThreadPool() self.pool.setMaxThreadCount(1) self.signals = WorkerSignals() class Window(QMainWindow): def __init__(self): super().__init__() self.setupUi(MainWindow) self.thread_pool = QThreadPool() def setupUi(self, MainWindow): MainWindow.setObjectName("ID Finder") MainWindow.resize(700, 600) MainWindow.setStyleSheet("background-color: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 white, stop: 0.4 rgba(152, 65, 30, 40),stop:1 rgba(0, 200, 230, 200));") self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.submit1 = QtWidgets.QPushButton(self.centralwidget) self.submit1.setGeometry(QtCore.QRect(290, 290, 93, 28)) self.submit1.setObjectName("Wake Bot") self.submit1.setStyleSheet("background-color: transparent;color:rgba(255,255,255,250);border-style: ridge;border-width : 2px; border-radius: 10px;border-color: cyan ;padding : 6px") self.label3 = QtWidgets.QLabel(self.centralwidget) self.pixmap = QtGui.QPixmap('instagram.webp') self.label3.setPixmap(self.pixmap) self.label3.setGeometry(200,-15,250,250) self.label3.setStyleSheet('background-color : transparent') self.userinput = QtWidgets.QLineEdit(self.centralwidget) self.userinput.setGeometry(QtCore.QRect(90, 200, 531, 41)) self.userinput.setObjectName("userinput") self.userinput.setStyleSheet("background-color: white;") self.userinput2 = QtWidgets.QLineEdit(self.centralwidget) self.userinput2.setGeometry(QtCore.QRect(90, 380, 220, 21)) self.userinput2.setObjectName("userinput2") self.userinput2.setStyleSheet("background-color: white;") self.refresh = QtWidgets.QPushButton(self.centralwidget) self.refresh.setGeometry(QtCore.QRect(290, 330, 93, 28)) self.refresh.setObjectName("refresh") self.refresh.setStyleSheet("background-color: gray;border-style: ridge ;border-width : 2px; border-radius: 10px;border-color: black ;padding : 6px") self.tb = QtWidgets.QTextBrowser(self.centralwidget) self.tb.setGeometry(QtCore.QRect(90,410,531,28)) self.tb.setObjectName("tb") self.tb.setAcceptRichText(True) self.tb.setOpenExternalLinks(True) self.tb.setStyleSheet("background-color: white;") self.pbar = QtWidgets.QProgressBar(self.centralwidget) self.pbar.setGeometry(251, 475, 200, 25) # adding action to push button self.label2 = QtWidgets.QLabel(self.centralwidget) self.label2.setText("Enter Username") self.label2.setStyleSheet("background-color: rgba(0,0,0,0);color:rgba(225,55,65,220);border-style: ridge ;border-width : 2px; border-radius: 10px;border-color: red ;padding : 1px;") font = QtGui.QFont("Bahnschrift") self.label2.setFont(font) self.label2.setGeometry(90,170,175,28) font2 = QtGui.QFont() font2.setPointSize(13) self.label2.setFont(font2) self.label5 = QtWidgets.QLabel(self.centralwidget) self.label5.setText("Enter File Name") self.label5.setStyleSheet("background-color: rgba(0,0,0,0);color:rgba(225,55,65,220);border-style: ridge ;border-width : 2px; border-radius: 10px;border-color: red ;padding : 1px;") font = QtGui.QFont("Bahnschrift") self.label5.setFont(font) self.label5.setGeometry(320,375,175,28) font2 = QtGui.QFont() font2.setPointSize(13) self.label5.setFont(font2) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 26)) self.menubar.setObjectName("menubar") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) ##Action Center self.submit1.pressed.connect(self.start_long_task1) self.refresh.clicked.connect(self.clear_text) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) def start_long_task1(self): self.submit1.setEnabled(False) worker = Worker(self.finderfunc) worker.signals.result.connect(self.initiald) worker.signals.finished.connect(self.progress_fn) worker.signals.progress.connect(self.progress_fn) # pool = QThreadPool.globalInstance() self.thread_pool.start(worker) def initiald(self): self.tb.append("Rise and Shine") def finderfunc(self,*args,**kwargs): self.tb.append("Initializing ..") bot = Client() bot.handle_exception try: bot.load_settings('ig_dump.json') except: bot.dump_settings('ig_dump.json') bot.login(username ="",password="") id_list = [] for x in range(0,101): try: user_id = bot.user_id_from_username(f'{self.userinput.text().strip()}{x}') id_list.append(user_id) time.sleep(25) self.pbar.setValue(x) except Exception as e1: self.tb.append(str(e1)) self.pbar.setValue(x) link_list = [] for id in id_list: try: user_info = bot.user_info(id).dict() if user_info['is_private'] is True: pass else: if int(user_info['follower_count'])<=20: link_list.append((["https://www.instagram.com/" + user_info['username'],str(user_info['follower_count'])])) else: pass except Exception as e2: self.tb.apppend(str(e2)) with open(f"{self.userinput2.text()}.csv","w") as f: writer = csv.writer(f) writer.writerows(link_list) def completep(self): self.tb.append("Completed . . .") def clear_text(self): self.userinput.clear() self.tb.clear() self.userinput2.clear() def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "Account Finder App")) self.submit1.setText(_translate("MainWindow", "Wake Bot")) self.refresh.setText(_translate("MainWindow", "Refresh")) # self.label.setText(_translate("MainWindow", "ACCOUNT FINDER")) self.label2.setText(_translate("MainWindow","Enter Username")) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) MainWindow = QtWidgets.QMainWindow() ui = Window() ui.setupUi(MainWindow) MainWindow.show() sys.exit(app.exec_())
问题根源与修复方案
1. 核心问题:工作线程直接操作UI组件
PyQt明确规定只有主线程能创建和操作UI元素,你的finderfunc在工作线程中直接调用self.tb.append()、self.pbar.setValue(),这会触发线程安全问题,导致界面卡顿、无响应。
2. 修复步骤
(1)修改工作线程逻辑,通过信号传递更新
在finderfunc中,不再直接操作UI,而是通过progress_callback信号把进度和日志信息发送到主线程:
def finderfunc(self,*args,**kwargs): progress_callback = kwargs.get('progress_callback') progress_callback.emit(0, "Initializing ..") bot = Client() bot.handle_exception try: bot.load_settings('ig_dump.json') except: bot.dump_settings('ig_dump.json') # 注意:填充有效的Instagram账号密码 bot.login(username ="your_username",password="your_password") id_list = [] for x in range(0,101): try: user_id = bot.user_id_from_username(f'{self.userinput.text().strip()}{x}') id_list.append(user_id) time.sleep(25) progress_callback.emit(x, f"已处理第{x}个账号") except Exception as e1: progress_callback.emit(x, f"错误:{str(e1)}") link_list = [] for idx, id in enumerate(id_list): current_progress = int((idx + 101) / 202 * 100) try: user_info = bot.user_info(id).dict() if user_info['is_private'] is True: progress_callback.emit(current_progress, f"跳过私有账号:{user_info['username']}") else: if int(user_info['follower_count'])<=20: link_list.append((["https://www.instagram.com/" + user_info['username'],str(user_info['follower_count'])])) progress_callback.emit(current_progress, f"找到符合条件账号:{user_info['username']}") else: progress_callback.emit(current_progress, f"粉丝数超标:{user_info['username']}") except Exception as e2: progress_callback.emit(current_progress, f"错误:{str(e2)}") with open(f"{self.userinput2.text()}.csv","w") as f: writer = csv.writer(f) writer.writerows(link_list) progress_callback.emit(100, "任务完成")
(2)在主线程中添加UI更新函数
新增两个函数分别处理进度条和日志更新:
def update_progress(self, value, msg): self.pbar.setValue(value) self.tb.append(msg) def task_finished(self): self.submit1.setEnabled(True) self.tb.append("Completed . . .")
(3)修改信号绑定逻辑
在start_long_task1中,正确绑定信号到对应的更新函数:
def start_long_task1(self): self.submit1.setEnabled(False) worker = Worker(self.finderfunc) worker.signals.result.connect(self.initiald) worker.signals.finished.connect(self.task_finished) worker.signals.progress.connect(self.update_progress) self.thread_pool.start(worker)
3. 其他优化点
- 修复
self.tb.apppend的拼写错误,改为append - 填充
bot.login的有效账号密码,否则任务无法正常执行 - 调整进度计算逻辑,让进度条能覆盖整个任务流程(从账号查询到信息筛选)
内容的提问来源于stack exchange,提问作者romancedawn
相关产品推荐
相关产品推荐

