我试图在PySide GUI应用程序中做一件相当常见的事情:我想将一些CPU密集型任务委托给一个后台线程,这样我的GUI就可以保持响应,甚至可以在计算过程中显示进度指示器。
下面是我正在做的事情(我在Python2.7上使用PySide 1.1.1,Linux x86_64):
import sys
import time
from PySide.QtGui import QMainWindow, QPushButton, QApplication, QWidget
from PySide.QtCore import QThread, QObject, Signal, Slot
class Worker(QObject):
done_signal = Signal()
def __init__(self, parent = None):
QObject.__init__(self, parent)
@Slot()
def do_stuff(self):
print "[thread %x] computation started" % self.thread().currentThreadId()
for i in range(30):
# time.sleep(0.2)
x = 1000000
y = 100**x
print "[thread %x] computation ended" % self.thread().currentThreadId()
self.done_signal.emit()
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
self.work_thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.work_thread)
self.work_thread.started.connect(self.worker.do_stuff)
self.worker.done_signal.connect(self.work_done)
def initUI(self):
self.btn = QPushButton('Do stuff', self)
self.btn.resize(self.btn.sizeHint())
self.btn.move(50, 50)
self.btn.clicked.connect(self.execute_thread)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Test')
self.show()
def execute_thread(self):
self.btn.setEnabled(False)
self.btn.setText('Waiting...')
self.work_thread.start()
print "[main %x] started" % (self.thread().currentThreadId())
def work_done(self):
self.btn.setText('Do stuff')
self.btn.setEnabled(True)
self.work_thread.exit()
print "[main %x] ended" % (self.thread().currentThreadId())
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
应用程序显示一个带有按钮的窗口。 当按下按钮时,我希望它在执行计算时禁用自身。然后,该按钮应重新启用。
相反,当我按下按钮时,整个窗口在计算过程中冻结,然后,当它完成时,我重新控制应用程序。按钮似乎从未被禁用过。
有趣的是,如果我用一个简单的time.sleep()替换do_stuff()
中的CPU密集型计算,程序就会按预期运行。
我不知道到底发生了什么,但是第二个线程的优先级似乎很高,以至于它实际上阻止了GUI线程被调度。如果第二个线程进入阻塞状态(就像在sleep()
中发生的那样),GUI实际上有机会按照预期运行和更新接口。我试图更改工作线程优先级,但看起来在Linux上无法完成。
另外,我试图打印线程ID,但我不确定我是否正确。如果我是,线程关联似乎是正确的。
我还用PyQt尝试了这个程序,其行为完全相同,因此出现了标记和标题。如果我可以用PyQt4而不是PySide运行它,我可以将整个应用程序切换到PyQt4
这可能是由持有Python的GIL的工作线程引起的。在一些Python实现中,一次只能执行一个Python线程。GIL防止其他线程执行Python代码,并在不需要GIL的函数调用期间释放。
例如,GIL是在实际IO期间发布的,因为IO是由操作系统而不是Python解释器处理的。
解决方案:
显然,您可以在工作线程中使用
time.sleep(0)
来屈服于其他线程(according to this SO question)。您必须定期自己调用time.sleep(0)
,并且GUI线程将只在后台线程调用此函数时运行。如果工作线程足够独立,则可以将其放入完全独立的进程中,然后通过通过管道发送pickled对象进行通信。在前台进程中,创建一个工作线程来处理后台进程的IO。由于工作线程将执行IO而不是CPU操作,因此它不会保存GIL,这将为您提供一个完全响应的GUI线程。
一些Python实现(JPython和IronPython)没有GIL。
CPython中的线程只对多路复用IO操作有用,而不是将CPU密集型任务放在后台。对于许多应用程序来说,CPython实现中的线程是从根本上被破坏的,并且很可能在可预见的未来保持这种状态。
最后,这对我的问题是有效的-所以代码可以帮助其他人。
相关问题 更多 >
编程相关推荐