PyQt 分离UI主线程与工作线程
2015-07-18 19:49
483 查看
前言
前几天刚学 PyQt 的图像界面,制作一个小窗口的时候,需要拉取网络验证码,当用户点击已有的验证码的时候,就开始获取下载新的验证码,然后刷新QLabel显示新的验证码。做出来之后,发现如果网络不通畅,特别是用户密码输入出错时,下载新的验证码图片特别慢,这时的登陆窗口就卡住了,不一会就变成了“未响应”,等了好一会下载完了,程序才恢复响应。
网上找了一下问题的原因,说是UI主线程和工作线程没有分开,使用urllib等库的时候堵塞主线程,系统就将程序判断为未响应了。做法说是耗时的工作要分开线程,要继承QThread类,要重写run函数等等等等,可惜都没有一个具体的例子说明,也就探索了许久。这里给出我的做法,也作为一个自己的笔记。
准备
Python 2.7PyQt4
sublime text 3
开始
刚开始用PyQt designer做出ui类,然后自己的窗口要么继承要么里面声明ui对象去使用里面的setupUi()函数。我用的是继承,然后调用类内部函数。
ui类代码如下:
# -*- coding: utf-8 -*- from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: def _fromUtf8(s): return s try: _encoding = QtGui.QApplication.UnicodeUTF8 def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig, _encoding) except AttributeError: def _translate(context, text, disambig): return QtGui.QApplication.translate(context, text, disambig) class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName(_fromUtf8("Dialog")) Dialog.resize(400, 300) self.pushButton = QtGui.QPushButton(Dialog) self.pushButton.setGeometry(QtCore.QRect(150, 160, 112, 34)) self.pushButton.setObjectName(_fromUtf8("pushButton")) self.retranslateUi(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog) def retranslateUi(self, Dialog): Dialog.setWindowTitle(_translate("Dialog", "Dialog", None)) self.pushButton.setText(_translate("Dialog", "干大事!", None)) if __name__ == "__main__": import sys app = QtGui.QApplication(sys.argv) Dialog = QtGui.QDialog() ui = Ui_Dialog() ui.setupUi(Dialog) Dialog.show() sys.exit(app.exec_())
执行效果如图:
用另一个文件新建一个类,继承上面的ui类:
# -*- coding: utf-8 -*- from PyQt4 import QtCore, QtGui #从 ui.py 文件里 import ui类 from example_ui import Ui_Dialog import sys import time #新建自己的窗口类,继承 QDialog 和 ui类 class MyDialog(QtGui.QDialog,Ui_Dialog): def __init__(self, parent=None): super(MyDialog, self).__init__(parent) #调用内部的 setupUi() ,本身对象作为参数 self.setupUi(self) #连接 QPushButton 的点击信号到槽 BigWork() self.pushButton.clicked.connect(self.BigWork) def BigWork(self): # 干一件大事... 耗时 10s time.sleep(10) if __name__ == '__main__': app = QtGui.QApplication(sys.argv) #新建类对象 Dialog = MyDialog() #显示类对象 Dialog.show() sys.exit(app.exec_())
运行效果如图:
额…和上面并没有什么不同,我们点击一下试试。
不出几秒,窗口就成了这样子。鼠标转几下之后,它又恢复了原样。
产生这个未响应的原因,是我的工作函数
BigWork()和ui主线程是同个线程,它干着大事的时候,ui主线程就没有办法刷新自己,因为路被大事堵住了,要等大事做完之后才能刷新,系统就认为这个窗口这么长时间没有刷新肯定挂了,就变成了未响应。而且,这让用户体验也变得非常低,窗口在等待的时候,不仅仅不能点击,连移动窗口都不行,如果等的久了,还可能被用户kill掉,所以,分离工作线程是非常必要的。
PyQt也给我们提供了这么一个类:QThread
通过继承它然后重写里面的
run()函数,就可以很容易的新建一个线程,达到多线程的任务。
我们新建一个py文件,就起名叫做threads.py,代码如下:
# -*- coding: utf-8 -*- from PyQt4 import QtCore, QtGui import time #继承 QThread 类 class BigWorkThread(QtCore.QThread): """docstring for BigWorkThread""" def __init__(self, parent=None): super(BigWorkThread, self).__init__(parent) #重写 run() 函数,在里面干大事。 def run(self): #大事 time.sleep(10)
相当于把
BigWork()函数里的任务移到这个
run()函数里来做。
要创建新进程也很简单,把原窗口类
BigWork()函数改一下就可以了,代码如下:
def BigWork(self): #import 自己的进程类 from threads import BigWorkThread #新建对象 self.bwThread = BigWorkThread() #开始执行run()函数里的内容 self.bwThread.start()
注意
为什么要将新进程对象声明为私有成员嘞?原因是,如果声明为局部变量,那么
BigWork()函数执行完
bwThread.start()这一句,也就是最后一句的时候,局部变量将会被销毁,子进程也就被kill了,这时候会报错:“QThread: Destroyed while thread is still running”。
网上有种说法,说可以调用
wait()函数等它执行完,但我测试了一下,
wait()函数的调用就不能退出主线程函数了…结果还是成了单线程。
高级用法
假如,我现在点一次按钮就干一次大事,但干着一次大事的时候,我不想同时开始干第二次大事,我就要把“干大事”这个按钮变成无效,等干完了第一次再恢复有效。这时就可以用到信号和槽,子进程有一个信号,连接着主窗口的一个函数,这个函数复制处理“子进程干完活了之后要干什么”这个问题。
(感觉还是单线程呀!然而,这么做就不会出现“未响应”的情况了)
再比如,原来的
BigWork()函数需要接受一个参数
t,来决定这个大事要干多久,我们就可以把这个参数放到子线程类的构造函数中。
再再比如,我要子线程执行完之后有返回值,就可以把这个返回值放到子进程的信号里,随着信号一起发回。当然,接受这个信号的槽的形参也要做相应的变化。
下面给出一个完整的例子,但不包括ui类的定义。
子进程定义:
# -*- coding: utf-8 -*- from PyQt4 import QtCore, QtGui import time #继承 QThread 类 class BigWorkThread(QtCore.QThread): """docstring for BigWorkThread""" #声明一个信号,同时返回一个list,同理什么都能返回啦 finishSignal = QtCore.pyqtSignal(list) #构造函数里增加形参 def __init__(self, t,parent=None): super(BigWorkThread, self).__init__(parent) #储存参数 self.t = t #重写 run() 函数,在里面干大事。 def run(self): #大事 time.sleep(self.t) #大事干完了,发送一个信号告诉主线程窗口 self.finishSignal.emit(['hello,','world','!'])
信号声明不能在
__init__()函数里,不然会报错:
AttributeError: 'PyQt4.QtCore.pyqtSignal' object has no attribute 'emit',具体原因还没想通…
主进程窗口的定义:
# -*- coding: utf-8 -*- from PyQt4 import QtCore, QtGui #从 ui.py 文件里 import ui类 from example_ui import Ui_Dialog import sys import time class MyDialog(QtGui.QDialog,Ui_Dialog): def __init__(self, parent=None): super(MyDialog, self).__init__(parent) #调用内部的 setupUi() ,本身对象作为参数 self.setupUi(self) #连接 QPushButton 的点击信号到槽 BigWork() self.pushButton.clicked.connect(self.BigWork) def BigWork(self): #把按钮禁用掉 self.pushButton.setDisabled(True) #import 自己的进程类 from threads import BigWorkThread #新建对象,传入参数 self.bwThread = BigWorkThread(int(1)) #连接子进程的信号和槽函数 self.bwThread.finishSignal.connect(self.BigWorkEnd) #开始执行 run() 函数里的内容 self.bwThread.start() #增加形参准备接受返回值 ls def BigWorkEnd(self,ls): print 'get!' #使用传回的返回值 for word in ls: print word, #恢复按钮 self.pushButton.setDisabled(False) if __name__ == '__main__': app = QtGui.QApplication(sys.argv) #新建类对象 Dialog = MyDialog() #显示类对象 Dialog.show() sys.exit(app.exec_())
嗯就是酱紫了,PyQt的线程分离就是这么简单。
关键词:PyQt4,Python ,多线程,信号槽,ui线程分离
相关文章推荐
- ValueError: bad transparency mask
- ValueError: bad transparency mask 分类: 小技巧 软件插件学习 2015-07-18 19:42 27人阅读 评论(0) 收藏
- 关于UITableView的性能优化(历上最全面的优化分析)
- POJ 2524 Ubiquitous Religions
- DP Codeforces Round #FF (Div. 1) A. DZY Loves Sequences
- zoj 1926 Guessing Game
- zoj 1926 Guessing Game
- iOS—— build 与version,InfoDictionary version的区别
- hdu 1711 Number Sequence(kmp)
- hdu 1711 Number Sequence(kmp)
- UI_cell 注册问题
- Android开发之应用程序窗体显示状态操作(requestWindowFeature()的应用)
- Android开发UI之android:gravity / android:layout_Gravity,android:padding / android:layout_margin属性区分
- mysql客户端乱码问题解决,uuid使用方法
- UI基础:UITableView的编辑和移动
- 两个对象用equals方法比较为true,它们的Hashcode值相同吗?
- 递推 Codeforces Round #186 (Div. 2) B. Ilya and Queries
- iOS_UITableView上拉加载,下拉刷新
- hdu 1159 Common Subsequence (dp乞讨LCS)
- requestWindowFeature的使用