我试图在PyQT GUI中实现线程但遇到麻烦。一些背景,我有一个独立的脚本,卸载了一些软件,删除了一些文件夹,然后重新安装了一个较新的版本。我使用线程模块删除文件夹,为每个文件夹启动一个新线程。有几个文件夹很大并需要一些时间,所以我会迭代单独的线程但跳过较大的文件夹并加入线程:
thread = threading.Thread(name=portalDirToDelete,target=deleteFolder,args=(portalDirToDelete,))
thread.start()
....
for thread in threading.enumerate():
if not "MainThread" in thread.getName() and not "content" in thread.getName() and not "temp" in thread.getName():
thread.join()
一旦我开始使用PyQT4创建UI,我发现线程在我尝试加入它们之前不会启动。我做了一些阅读,并了解到使用线程模块将不再工作,(How to keep track of thread progress in Python without freezing the PyQt GUI?)所以我开始研究QThreads,但没有多少运气。下面是我的其他脚本正在做的简化版本:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'Samples\ThreadUI1.ui'
#
# Created by: PyQt4 UI code generator 4.11.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
import shutil, os, time
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 workerThread(QtCore.QObject):
finished = QtCore.pyqtSignal()
def deleteFolder(self,path):
if os.path.exists(path):
shutil.rmtree(path)
self.finished.emit()
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName(_fromUtf8("Form"))
Form.resize(115, 66)
self.runApp = QtGui.QPushButton(Form)
self.runApp.setGeometry(QtCore.QRect(20, 20, 75, 23))
self.runApp.setObjectName(_fromUtf8("runApp1"))
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
self.runApp.clicked.connect(lambda:self.runSetups())
def retranslateUi(self, Form):
Form.setWindowTitle(_translate("Form", "Form", None))
self.runApp.setText(_translate("Form", "Run App", None))
def usingMoveToThread(self,path):
self.app = QtCore.QCoreApplication([])
self.objThread = QtCore.QThread()
self.obj = workerThread()
self.obj.moveToThread(self.objThread)
self.obj.finished.connect(self.objThread.quit)
self.objThread.started.connect(self.obj.deleteFolder(path))
self.objThread.finished.connect(app.exit)
self.objThread.start()
def runSetups(self):
self.usingMoveToThread(r"C:\arcgisportal")
for x in range(1,11):
print(x)
time.sleep(1)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
Form = QtGui.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())
我认为这可能是错的,但我的方法是启动删除新线程中的文件夹的过程,然后查看它是否继续在循环中打印。我一直在使用Threading and PyQT作为主要指南。关于是否对QThread进行子类化似乎存在不同的意见,所以我在这篇博客(https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/)中没有这样做。
我使用的是Python 2.7,PyQt4 4.11.4,这个应用程序是通过UI Designer制作的。我知道更新生成的ui / py文件并不是一个好主意,我在其他地方没有这样做,只是将这个样本作为上下文提供。
感谢任何帮助!
更新
如果我打开一个模态QMessageBox()并添加一个关闭QMessageBox的按钮,当它打开时,将删除较大文件夹中的文件夹。关闭QMessageBox()后,文件夹就会停止删除,因此我对于线程和用户界面还不清楚。
答案 0 :(得分:1)
你写的内容有几个问题(希望我已经把它们全部抓住了,我不愿意在我的机器上运行与删除文件相关的代码!)
您正在QCoreApplication
方法中构建新的usingMoveToThread
。每个python实例应该只有一个QApplication
。我不确定这样做会做什么,但我怀疑它解释了你在创建模态对话框时看到的行为。创建QApplication
(或QCoreApplication
)启动主Qt事件循环,该循环处理来自处理信号/调用槽和处理窗口重绘的所有内容。您已在主线程中创建了第二个事件循环,但未通过调用exec_()
启动它。我怀疑在创建对话框时,这是调用exec_()
(或创建第三个事件循环,以某种方式修复第二个事件循环创建的问题)。 无论如何都没有理由手动创建第二个事件循环。主要的就是处理线程的东西。(注意:每个pyqt应用程序可以有多个事件循环,例如线程可以有一个事件循环(见下文),对话框有时会有自己的事件循环。但是,Qt为您处理创作!)
runSetups
中的循环将阻止主线程,因此您的GUI将无法响应。这不应该影响线程,但是如果你只是要阻止GUI,它就会引起一个问题:将它卸载到线程是什么意思!请注意,Python GIL无论如何都会阻止多个线程同时运行,因此如果您希望多个线程同时删除单独的文件夹,则需要考虑多处理而不是Python GIL。但是,这可能是过早的优化。可能存在磁盘I / O注意事项,您可能会发现其中一种方法可以提供任何有意义的加速。
主要问题是以下一行:self.objThread.started.connect(self.obj.deleteFolder(path))
。您实际上在主线程中运行deleteFolder(path)
,并将该函数的返回值传递给connect
方法。这导致在线程中没有任何运行,并且主线程中发生的一切(并阻止GUI)。出现这个错误是因为您想要将参数传递给deleteFolder
方法。但你不能,因为你没有发出started
信号(Qt这样做),所以你不能提供参数。通常,您可以通过在lambda
或partial
中包装方法来解决此问题,但这会在处理线程时带来其他问题(请参阅here)。相反,您需要在Ui_Form
对象中定义一个新信号,该信号采用str
参数,将该信号连接到deleteFolder
插槽并在启动线程后手动发出信号。
这样的事情:
class Ui_Form(object):
deleteFolder = QtCore.pyqtSignal(str)
...
def usingMoveToThread(self,path):
self.objThread = QtCore.QThread()
self.obj = workerThread()
self.obj.moveToThread(self.objThread)
self.deleteFolder.connect(self.obj.deleteFolder)
self.objThread.start()
self.deleteFolder.emit(path)
您可能想查看this问题,了解有关此方法的更多信息(基本上,Qt可以处理不同线程中信号/插槽之间的连接,因为每个线程都有自己的事件循环)。
答案 1 :(得分:-2)
# your app initialization:
# application = QApplication(sys.argv)
# main_window.show()
# application.exec_()
...
thread = threading.Thread(name=portalDirToDelete,target=deleteFolder,args=(portalDirToDelete,))
thread.start()
....
for thread in threading.enumerate():
if not "MainThread" in thread.getName() and not "content" in thread.getName() and not "temp" in thread.getName():
while thread.is_alive():
application.processEvents()