了解线程和PyQT

时间:2016-12-30 20:03:47

标签: python multithreading pyqt

我试图在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()后,文件夹就会停止删除,因此我对于线程和用户界面还不清楚。

2 个答案:

答案 0 :(得分:1)

你写的内容有几个问题(希望我已经把它们全部抓住了,我不愿意在我的机器上运行与删除文件相关的代码!)

  1. 您正在QCoreApplication方法中构建新的usingMoveToThread。每个python实例应该只有一个QApplication。我不确定这样做会做什么,但我怀疑它解释了你在创建模态对话框时看到的行为。创建QApplication(或QCoreApplication)启动主Qt事件循环,该循环处理来自处理信号/调用槽和处理窗口重绘的所有内容。您已在主线程中创建了第二个事件循环,但未通过调用exec_()启动它。我怀疑在创建对话框时,这是调用exec_()(或创建第三个事件循环,以某种方式修复第二个事件循环创建的问题)。 无论如何都没有理由手动创建第二个事件循环。主要的就是处理线程的东西。(注意:每个pyqt应用程序可以有多个事件循环,例如线程可以有一个事件循环(见下文),对话框有时会有自己的事件循环。但是,Qt为您处理创作!)

  2. runSetups中的循环将阻止主线程,因此您的GUI将无法响应。这不应该影响线程,但是如果你只是要阻止GUI,它就会引起一个问题:将它卸载到线程是什么意思!请注意,Python GIL无论如何都会阻止多个线程同时运行,因此如果您希望多个线程同时删除单独的文件夹,则需要考虑多处理而不是Python GIL。但是,这可能是过早的优化。可能存在磁盘I / O注意事项,您可能会发现其中一种方法可以提供任何有意义的加速。

  3. 主要问题是以下一行:self.objThread.started.connect(self.obj.deleteFolder(path))。您实际上在主线程中运行deleteFolder(path),并将该函数的返回值传递给connect方法。这导致在线程中没有任何运行,并且主线程中发生的一切(并阻止GUI)。出现这个错误是因为您想要将参数传递给deleteFolder方法。但你不能,因为你没有发出started信号(Qt这样做),所以你不能提供参数。通常,您可以通过在lambdapartial中包装方法来解决此问题,但这会在处理线程时带来其他问题(请参阅here)。相反,您需要在Ui_Form对象中定义一个新信号,该信号采用str参数,将该信号连接到deleteFolder插槽并在启动线程后手动发出信号。

  4. 这样的事情:

    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()