PyQt5执行耗时操作导致主页面暂时性卡死问题

问题: PyQt5主界面,如果某些操作比较耗时,比如点击按钮执行某个脚本、点击按钮从网络上读取数据等,则点击按钮后,很可能造成整个主窗口卡死,无法执行窗口最大化、最小化、文本输入、按钮点击等其他操作。

例子如下:

    程序执行后,有个按钮和一个QLabel显示框,点击按钮后,会计算1+2+...+50000000,整个计算需要十几秒,计算过程中,主界面卡死,无法执行窗口最大化等操作。

# coding=utf-8
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QHBoxLayout, QPushButton)
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt, QThread, pyqtSignal


class MyGui(QWidget):
"""基础Gui页面"""

def __init__(self):
super().__init__()
self.init_ui() # 主窗口

def init_ui(self):
# self.setGeometry(300, 300, 600, 200) # 设置主窗口的位置和大小
self.setWindowTitle('Simple Little tools') # 设置主窗口的标题
self.setWindowIcon(QIcon('pics/icon1.gif')) # 设置主窗口的图标(左上角)

# 通用样式。页面上创建的一些部件,会默认使用设置的通用样式
self.setStyleSheet(
'QPushButton{font-weight: bold; background: skyblue; border-radius: 14px;'
'width: 300px; height: 28px; font-size: 20px; text-align: center;}'
'QPushButton:hover{background: rgb(50, 150, 255);}'
'QLabel{font-weight: bold; font-size: 20px; color: orange}'
)
self.main_layout() # 布局函数
self.show() # 显示窗口

def main_layout(self):

btn = QPushButton(' 点击计算1+2+...+50000000 ')
show_label = QLabel(' ')
show_label.setStyleSheet('color: black; background: skyblue; border-radius: 13px;')
hbox = QHBoxLayout()
hbox.addWidget(btn)
hbox.addWidget(show_label)
hbox.addStretch()
self.setLayout(hbox)
btn.clicked.connect(lambda: self.update_text(show_label))

def update_text(self, w):
txt = str(self.cal_sum(50000000))
w.setText(' ' + txt + ' ')

def cal_sum(self, n=100):
sum, k = 0, 0
while k <= n:
sum += k
k += 1
return sum


if __name__ == '__main__':
app = QApplication(sys.argv)
qt = MyGui()
sys.exit(app.exec_())

几个截图如下::

          

截图1是程序执行完的情况,主窗口显示了一个按钮个一个QLabel; 截图2是点击按钮执行累加操作的情况,此时主页面卡死,
窗口无法最大化、最小化等; 截图3是加法操作执行完的情况,此时界面恢复正常。

推测原因: 在PyQt中,GUI界面本身就是一个主线程,当点击按钮执行累加操作时,因为这个累加操作直接跑在这个主线程上,GUI需要等待累加操作完成后才会响应,在等待这段时间,整个GUI就处于卡死的状态。如果这个操作是一个死循环(比如按秒更新时间),在windows下,系统会认为这个程序运行出错了,会自动显示未响应,进而关闭程序。

这里可以考虑另开一个线程来执行这个累加操作。(PyQt5的QThread)

# coding=utf-8
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QHBoxLayout, QPushButton)
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt, QThread, pyqtSignal


class MyGui(QWidget):
"""基础Gui页面"""

def __init__(self):
super().__init__()
self.init_ui() # 主窗口

def init_ui(self):
# self.setGeometry(300, 300, 600, 200) # 设置主窗口的位置和大小
self.setWindowTitle('Simple Little tools') # 设置主窗口的标题
self.setWindowIcon(QIcon('pics/icon1.gif')) # 设置主窗口的图标(左上角)

# 通用样式。页面上创建的一些部件,会默认使用设置的通用样式
self.setStyleSheet(
'QPushButton{font-weight: bold; background: skyblue; border-radius: 14px;'
'width: 300px; height: 28px; font-size: 20px; text-align: center;}'
'QPushButton:hover{background: rgb(50, 150, 255);}'
'QLabel{font-weight: bold; font-size: 20px; color: orange}'
)
self.main_layout() # 布局函数
self.show() # 显示窗口

def main_layout(self):
self.btn = QPushButton(' 点击计算1+2+...+50000000 ')
self.show_label = QLabel(' ')
self.show_label.setStyleSheet('color: black; background: skyblue; border-radius: 13px;')
hbox = QHBoxLayout()
hbox.addWidget(self.btn)
hbox.addWidget(self.show_label)
hbox.addStretch()
self.setLayout(hbox)
self.btn.clicked.connect(self.update) # 按钮点击后创建一个新的线程

def update(self):
self.btn.setText(' 计算中... ') # 主页面按钮点击后更新按钮文本
self.btn.setEnabled(False) # 将按钮设置为不可点击
self.cal = CalSumTheard() # 创建一个线程
self.cal._sum.connect(self.update_sum) # 线程发过来的信号挂接到槽函数update_sum
self.cal.start() # 线程启动

def update_sum(self, r):
self.show_label.setText(' ' + r + ' ') # 信号发过来时,更新QLabel内容
self.btn.setText(' 点击计算1+2+...+50000000 ') # 更新按钮
self.btn.setEnabled(True) # 让按钮恢复可点击状态


class CalSumTheard(QThread):
"""该线程用于计算耗时的累加操作"""
_sum = pyqtSignal(str) # 信号类型 str

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

def run(self):
s, k = 0, 0
while k <= 50000000:
s += k
k += 1
self._sum.emit(str(s)) # 计算结果完成后,发送结果


if __name__ == '__main__':
app = QApplication(sys.argv)
qt = MyGui()
sys.exit(app.exec_())
效果如下:

          

截图1就是程序执行完成后的效果;截图2就是按钮点击后的效果(新加了个点击按钮后更新按钮文字和将按钮设置为不可点击),
此时主页面可以正常拖动、最大、最小化;截图三累加操作执行完成后的效果(鼠标放在按钮会变色,此时鼠标是放在按钮上的)


另写了一个QThread,按秒更新时间
# coding=utf-8
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QFrame,
QSplitter, QHBoxLayout, QVBoxLayout)
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt, QThread, pyqtSignal
import time


class MyGui(QWidget):
"""基础Gui页面"""

def __init__(self):
super().__init__()
self.update() # 因为没有按钮触发新线程,因此需要在初始化时就先执行线程函数
self.init_ui() # 主窗口

def init_ui(self):
self.setGeometry(300, 300, 800, 400) # 设置主窗口的位置和大小
self.setWindowTitle('Simple Little tools') # 设置主窗口的标题
self.setWindowIcon(QIcon('pics/icon1.gif')) # 设置主窗口的图标(左上角)

# 通用样式。页面上创建的一些部件,会默认使用设置的通用样式
self.setStyleSheet(
'QPushButton{font-weight: bold; background: skyblue; border-radius: 14px;'
'width: 64px; height: 28px; font-size: 20px; text-align: center;}'
'QPushButton:hover{background: rgb(50, 150, 255);}'
'QLabel{font-weight: bold; font-size: 20px; color: orange}'
)
self.main_layout() # 布局函数
self.show() # 显示窗口

def main_layout(self):
hbox = QHBoxLayout() # 外层大盒
top_left = QFrame()
top_left.setFrameShape(QFrame.StyledPanel)
splitter_top = QSplitter(Qt.Horizontal) # 横向QSpli
splitter_top.addWidget(top_left)

# 当前时间显示
current_time_text_label = QLabel('当前时间:')
self.current_time_show_label = QLabel()
self.current_time_show_label.setStyleSheet('color: black; background: skyblue;'
'border-radius: 13px; font-size: 26px')
current_time_hbox = QHBoxLayout()
current_time_hbox.addWidget(current_time_text_label)
current_time_hbox.addWidget(self.current_time_show_label)
current_time_hbox.addStretch()

current_time_vbox = QVBoxLayout()
current_time_vbox.addLayout(current_time_hbox)
current_time_vbox.addStretch()

top_left.setLayout(current_time_vbox)
hbox.addWidget(splitter_top)
self.setLayout(hbox)

def update(self):
self.current = CurrentTimeThread() # 创建一个线程
self.current._update_time.connect(self.update_time) # 连接到槽函数update_time上
self.current.start()

def update_time(self, curr_time):
self.current_time_show_label.setText(' ' + curr_time + ' ') # 更信QLabel的时间


class CurrentTimeThread(QThread):
_update_time = pyqtSignal(str) # 信号类型 str

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

def run(self):
while 1:
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
self._update_time.emit(current_time)
time.sleep(1) # 每秒发送一个信号


if __name__ == '__main__':
app = QApplication(sys.argv)
qt = MyGui()
sys.exit(app.exec_())
效果图:

  

截图中看不到效果,实际效果是每秒都会更新时间,这种循环更新的还可以使用QTimer来处理。

总结:
1. 比较耗时的任务最好使用新线程去处理(QThread),主线程仅仅用于GUI显示
2. 循环任务可以使用新线程去处理,也可以使用QTimer去处理(这里没有演示QTimer)
3. 新线程要创建了才能正常使用。可以按钮触发创建,可以在初始化页面的时候就创建
posted @ 2020-11-23 19:53  Lipx9527  阅读(8995)  评论(0编辑  收藏  举报