PyQt中的QThread会阻塞主线程

4
我正在尝试创建一个简单的线程应用程序,其中有一个方法执行一些长时间处理的任务,一个小部件显示加载条和取消按钮。
我的问题是,无论我如何实现线程,它都不会真正地进行线程操作 - 一旦线程开始,用户界面就会被锁定。我已经阅读了关于这个问题的所有教程和帖子,但我现在不知所措,只能向社区求助来解决我的问题!
最初我尝试了子类化QThread,直到互联网上说这样做是错误的。然后我尝试了moveToThread方法,但没有任何改进。
初始化代码:
loadingThreadObject = LoadThread(arg1)
loadingThread = PythonThread()
loadingThreadObject.moveToThread(loadingThread)
loadingThread.started.connect(loadingThreadObject.load)
loadingThread.start()

PythonThread类(显然QThreads在pyQt中存在缺陷,除非你这样做,否则不会启动):

class PythonThread (QtCore.QThread):
    def __init__(self, parent=None):
        QtCore.QThread.__init__(self, parent)

    def start(self):
        QtCore.QThread.start(self)

    def run(self):
        QtCore.QThread.run(self)

LoadThread类:

class LoadThread (QtCore.QObject):
    results = QtCore.Signal(tuple)

    def __init__ (self, arg):
         # Init QObject
         super(QtCore.QObject, self).__init__()

         # Store the argument
         self.arg = arg

    def load (self):
         #
         # Some heavy lifting is done
         #

         loaded = True
         errors = []

         # Emits the results
         self.results.emit((loaded, errors))

任何帮助都将不胜感激!
谢谢。 Ben。

“重活”究竟是什么性质?这个问题的答案可能会影响到线程是否能够提供任何好处(因为Python的GIL所施加的限制)。 - ekhumoro
这主要涉及到 SQL 查询。不过我对响应式用户界面的兴趣比性能优势更大。 - Ben
我没有在谈论性能。如果 GIL 在长时间运行的任务期间没有被释放,那么仅仅使用线程是无法避免阻塞的。需要将任务分成块,并定期向主 GUI 线程发送信号,以便它可以处理任何挂起的事件(即调用 qApp.processEvents() 或其他方法)。 - ekhumoro
好的,那么运行SQL查询会锁定GIL吗?如果是这样的话,我有点遇到麻烦了,因为查询可能需要几分钟才能运行... - Ben
我对SQL的了解非常有限,但很可能每当可能时GIL都会被释放。虽然这可能取决于您使用的具体SQL库。 - ekhumoro
2个回答

2

问题出现在我使用的SQL库上(一个定制的内部解决方案),它被发现不是线程安全的,因此执行了阻塞查询。

如果您遇到类似的问题,请先尝试删除SQL调用并查看是否仍然阻塞。如果这可以解决阻塞问题,请尝试使用原始SQL通过MySQLdb(或等效于您使用的数据库类型)重新引入查询。这将诊断问题是否与您选择的SQL库有关。


1

started 信号连接的函数将在主GUI线程中运行它所连接的线程。然而,QThread 的 start() 函数会在线程初始化后在该线程中执行其 run() 方法,因此应创建 QThread 的子类并使其 run 方法运行要执行的 LoadThread.load 函数。不需要从 PythonThread 继承。应使用 QThread 子类的 start() 方法来启动线程。

PS: 由于在这种情况下,QThread 的子类的 run() 方法仅调用 LoadThread.load(),因此可以简单地将 run() 方法设置为 LoadThread.load

class MyThread(QtCore.QThread):
    run = LoadThread.load # x = y in the class block sets the class's x variable to y

一个例子:

import time
from PyQt4 import QtCore, QtGui
import sys
application = QtGui.QApplication(sys.argv)

class LoadThread (QtCore.QObject):
    results = QtCore.pyqtSignal(tuple)

    def __init__ (self, arg):
         # Init QObject
         super(QtCore.QObject, self).__init__()

         # Store the argument
         self.arg = arg

    def load(self):
         #
         # Some heavy lifting is done
         #
         time.sleep(5)
         loaded = True
         errors = []

         # Emits the results
         self.results.emit((loaded, errors))

l = LoadThread("test")

class MyThread(QtCore.QThread):
    run = l.load

thread = MyThread()

button = QtGui.QPushButton("Do 5 virtual push-ups")
button.clicked.connect(thread.start)
button.show()
l.results.connect(lambda:button.setText("Phew! Push ups done"))
application.exec_()

我也遇到了类似的问题,你找到解决方案了吗? - user2145312
嗨,@user2145312,没错--我的问题是由于我使用的SQL库(一种自定义的内部解决方案)不支持多线程,因此执行了阻塞查询。如果你的问题也与SQL有关,请尝试先移除SQL调用并查看是否仍会出现阻塞。如果这能解决阻塞问题,再尝试使用MySQLdb(或适用于你所使用的数据库类型的等效库)通过原始SQL重新引入查询。这将诊断问题是否与你选择的SQL库有关。 - Ben

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接