python GUI库图形界面开发之PyQt5 UI主线程与耗时线程分离详细方法实例


Posted in Python onFebruary 26, 2020

在做界面开发时,无论是移动端的Android,还是我们这里讲的PyQt5,经常会有一个界面开发准则,那就是UI主线程与耗时子线程一定要分开,主线程负责刷新界面,耗时操作,如网络交互、磁盘IO等,都应该放在子线程里执行,它们各司其职,保证系统正常运行,提升整体用户体验。

软硬件环境

windows 10 64bit

PyQt5

Anaconda3 with python 3.6.5

实例代码

首先看下工程目录结构

python GUI库图形界面开发之PyQt5 UI主线程与耗时线程分离详细方法实例

main.py,这是工程入口文件,它负责创建app

# -*- coding: utf-8 -*-

import sys

from PyQt5.QtWidgets import QApplication

from gui.mainwindow import MainWindow

if __name__ == '__main__':

  app = QApplication(sys.argv)
  main_window = MainWindow()
  main_window.show()
  sys.exit(app.exec_())

ui_mainwindow.py,负责界面的绘制,这个文件通过designer图形化工具作图然后使用pyuic工具生成对应的python代码

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file '.\mainwindow.ui'
#
# Created by: PyQt5 UI code generator 5.6
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
  def setupUi(self, MainWindow):
    MainWindow.setObjectName("MainWindow")
    MainWindow.resize(800, 600)
    MainWindow.setMinimumSize(QtCore.QSize(800, 600))
    MainWindow.setMaximumSize(QtCore.QSize(800, 600))
    self.centralwidget = QtWidgets.QWidget(MainWindow)
    self.centralwidget.setObjectName("centralwidget")
    self.button_ok = QtWidgets.QPushButton(self.centralwidget)
    self.button_ok.setGeometry(QtCore.QRect(260, 220, 230, 140))
    self.button_ok.setMinimumSize(QtCore.QSize(230, 140))
    self.button_ok.setMaximumSize(QtCore.QSize(230, 140))
    font = QtGui.QFont()
    font.setPointSize(50)
    self.button_ok.setFont(font)
    self.button_ok.setFocusPolicy(QtCore.Qt.TabFocus)
    self.button_ok.setObjectName("button_ok")
    MainWindow.setCentralWidget(self.centralwidget)
    self.statusbar = QtWidgets.QStatusBar(MainWindow)
    self.statusbar.setObjectName("statusbar")
    MainWindow.setStatusBar(self.statusbar)
    self.menubar = QtWidgets.QMenuBar(MainWindow)
    self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 23))
    self.menubar.setObjectName("menubar")
    self.menuFile = QtWidgets.QMenu(self.menubar)
    self.menuFile.setObjectName("menuFile")
    self.menuHelp = QtWidgets.QMenu(self.menubar)
    self.menuHelp.setObjectName("menuHelp")
    MainWindow.setMenuBar(self.menubar)
    self.actionExit = QtWidgets.QAction(MainWindow)
    self.actionExit.setObjectName("actionExit")
    self.actionCopy = QtWidgets.QAction(MainWindow)
    self.actionCopy.setObjectName("actionCopy")
    self.actionPaste = QtWidgets.QAction(MainWindow)
    self.actionPaste.setObjectName("actionPaste")
    self.actionCut = QtWidgets.QAction(MainWindow)
    self.actionCut.setObjectName("actionCut")
    self.actionHelp = QtWidgets.QAction(MainWindow)
    self.actionHelp.setObjectName("actionHelp")
    self.actionAbout = QtWidgets.QAction(MainWindow)
    self.actionAbout.setObjectName("actionAbout")
    self.action_query = QtWidgets.QAction(MainWindow)
    self.action_query.setObjectName("action_query")
    self.action_backupDB = QtWidgets.QAction(MainWindow)
    self.action_backupDB.setObjectName("action_backupDB")
    self.action_reset_mac = QtWidgets.QAction(MainWindow)
    self.action_reset_mac.setObjectName("action_reset_mac")
    self.menuFile.addSeparator()
    self.menuFile.addAction(self.actionExit)
    self.menuFile.addSeparator()
    self.menuHelp.addSeparator()
    self.menuHelp.addSeparator()
    self.menuHelp.addAction(self.actionAbout)
    self.menuHelp.addSeparator()
    self.menubar.addAction(self.menuFile.menuAction())
    self.menubar.addAction(self.menuHelp.menuAction())

    self.retranslateUi(MainWindow)
    QtCore.QMetaObject.connectSlotsByName(MainWindow)

  def retranslateUi(self, MainWindow):
    _translate = QtCore.QCoreApplication.translate
    MainWindow.setWindowTitle(_translate("MainWindow", "分离UI主线程和工作线程"))
    self.button_ok.setText(_translate("MainWindow", "确定"))
    self.menuFile.setTitle(_translate("MainWindow", "File"))
    self.menuHelp.setTitle(_translate("MainWindow", "Help"))
    self.actionExit.setText(_translate("MainWindow", "退出"))
    self.actionHelp.setText(_translate("MainWindow", "软件使用说明"))
    self.actionAbout.setText(_translate("MainWindow", "关于"))

mainwindow.py,主要负责界面上控件的事件处理

import time

from PyQt5.QtWidgets import QMainWindow

from gui.ui_mainwindow import *


class MainWindow(QMainWindow, Ui_MainWindow):

  def __init__(self, parent=None):
    super(MainWindow, self).__init__(parent)
    self.setupUi(self)

    # 绑定点击事件
    self.button_ok.clicked.connect(self.button_start)

  def button_start(self):

    self.button_ok.setChecked(True)
    self.button_ok.setDisabled(True)

    time.sleep(20)

这里我们使用time.sleep(20)来模拟耗时任务,执行python main.py后一会,界面就会出现无响应,假死的现象,等到20秒过后,界面又恢复了正常,用户体验非常差。

python GUI库图形界面开发之PyQt5 UI主线程与耗时线程分离详细方法实例

其实要解决这个问题,也非常简单。我们将UI主线程中的time.sleep(20)移动到子线程中就可以了。PyQt5中提供了线程类QThread,我们继承它并重写它的run方法,新建一个新的文件threads.py

# -*- coding: utf-8 -*-
import time

from PyQt5.QtCore import QThread, pyqtSignal

class WorkThread(QThread):

  # 使用信号和UI主线程通讯,参数是发送信号时附带参数的数据类型,可以是str、int、list等
  finishSignal = pyqtSignal(str)

  # 带参数示例
  def __init__(self, ip, port, parent=None):
    super(WorkThread, self).__init__(parent)

    self.ip = ip
    self.port = port

  def run(self):
    '''
    重写
    '''

    print('=============sleep======ip: {}, port: {}'.format(self.ip, self.port))
    time.sleep(20)

    self.finishSignal.emit('This is a test.')
    return

注意到这里我们使用了pyqtSignal,我们使用它来跟UI主线程通讯,一般用于界面元素的刷新,在子线程的最后,我们发送这个信号。

对应的mainwindow.py,需要进行如下修改

from gui.threads import WorkThread

# 其它部分省略
def button_start(self):

  print('button_start clicked.')

  # 设置按钮不可用
  self.button_ok.setChecked(True)
  self.button_ok.setDisabled(True)

  self.th = WorkThread(ip='192.168.1.1', port=4000)

  # 将线程th的信号finishSignal和UI主线程中的槽函数button_finish进行连接
  self.th.finishSignal.connect(self.button_finish)

  # 启动线程
  self.th.start()

def button_finish(self, msg):

  print('msg: {}'.format(msg))

  # 设置按钮可用
  self.button_ok.setChecked(False)
  self.button_ok.setDisabled(False)

一顿操作之后,再次执行python main.py,界面就再也不会出现No Resonding的提示了,可以在子线程执行过程中可以随意操作界面上的其它控件

更多相关知道请查看下面的相关链接

Python 相关文章推荐
Python中的高级函数map/reduce使用实例
Apr 13 Python
在Python的web框架中配置app的教程
Apr 30 Python
Python 字典与字符串的互转实例
Jan 13 Python
Python整型运算之布尔型、标准整型、长整型操作示例
Jul 21 Python
ubuntu16.04制作vim和python3的开发环境
Sep 23 Python
python实现反转部分单向链表
Sep 27 Python
Python一行代码实现快速排序的方法
Apr 30 Python
python Popen 获取输出,等待运行完成示例
Dec 30 Python
jupyter notebook 增加kernel教程
Apr 10 Python
python异常处理之try finally不报错的原因
May 18 Python
Visual Studio Code搭建django项目的方法步骤
Sep 17 Python
使用python绘制横竖条形图
Apr 21 Python
python——全排列数的生成方式
Feb 26 #Python
python GUI库图形界面开发之pyinstaller打包python程序为exe安装文件
Feb 26 #Python
python GUI库图形界面开发之PyQt5中QWebEngineView内嵌网页与Python的数据交互传参详细方法实例
Feb 26 #Python
python自动点赞功能的实现思路
Feb 26 #Python
python GUI库图形界面开发之PyQt5时间控件QTimer详细使用方法与实例
Feb 26 #Python
python GUI库图形界面开发之PyQt5窗口控件QWidget详细使用方法
Feb 26 #Python
python GUI库图形界面开发之PyQt5窗口类QMainWindow详细使用方法
Feb 26 #Python
You might like
php 数组二分法查找函数代码
2010/02/16 PHP
PHP 获取客户端真实IP地址多种方法小结
2010/05/15 PHP
PHP生成自适应大小的缩略图类及使用方法分享
2014/05/06 PHP
PHP答题类应用接口实例
2015/02/09 PHP
PHP中使用Memache作为进程锁的操作类分享
2015/03/30 PHP
php序列化函数serialize() 和 unserialize() 与原生函数对比
2015/05/08 PHP
动态加载JS文件的三种方法
2013/11/08 Javascript
写JQuery插件的基本知识
2013/11/25 Javascript
javascript中Date format(js日期格式化)方法小结
2015/12/17 Javascript
详解Angularjs filter过滤器
2016/02/06 Javascript
Node.js Addons翻译(C/C++扩展)
2016/06/12 Javascript
详解Angular的8个主要构造块
2017/06/20 Javascript
详解angularjs获取元素以及angular.element()用法
2017/07/25 Javascript
浅谈webpack打包之后的文件过大的解决方法
2018/03/07 Javascript
Vue无限滑动周选择日期的组件的示例代码
2018/07/18 Javascript
JQuery Ajax动态加载Table数据的实例讲解
2018/08/09 jQuery
修改vue+webpack run build的路径方法
2018/09/01 Javascript
详解Jest结合Vue-test-utils使用的初步实践
2019/06/27 Javascript
400多行Python代码实现了一个FTP服务器
2012/05/10 Python
巧用Python装饰器 免去调用父类构造函数的麻烦
2012/05/18 Python
python机器学习理论与实战(二)决策树
2018/01/19 Python
如何利用Python识别图片中的文字
2020/05/31 Python
基于CSS3实现的漂亮Menu菜单效果代码
2015/09/10 HTML / CSS
获取邓白氏信用报告:Dun & Bradstreet
2019/01/22 全球购物
彪马俄罗斯官网:PUMA俄罗斯
2019/07/13 全球购物
职业生涯规划书前言
2014/04/15 职场文书
《孙权劝学》教学反思
2014/04/23 职场文书
文明班集体申报材料
2014/05/23 职场文书
服装设计专业求职信
2014/06/16 职场文书
安全口号大全
2014/06/21 职场文书
升国旗演讲稿
2014/09/05 职场文书
工作期间打牌检讨书范文
2014/11/20 职场文书
新婚姻法离婚协议书范文
2014/11/30 职场文书
员工辞职信范文
2015/03/02 职场文书
2019年大学推荐信
2019/06/24 职场文书
springboot+rabbitmq实现智能家居实例详解
2022/07/23 Java/Android