You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 15:35:29