PyQt, QThread, GIL, 图形界面
我有一个用Python写的图形界面和程序逻辑。我经常通过调用 urllib.requests
来请求网络信息,这导致图形界面变得无响应,尽管这些调用是用 QThread
包裹的。我觉得这可能是因为 GIL
的原因。但是,如果我在PyQt应用中使用 QThread
,那么它的作用是什么呢?如果我不能让代码异步运行的话。
--代码--
qtthreaddecorator.py:
from PyQt4 import QtCore
class Worker(QtCore.QThread):
def __init__(self, thread_name, finished_slot, function, *args, **kwargs):
QtCore.QThread.__init__(self)
self._thread_name = thread_name
self._function = function
self._args = args
self._kwargs = kwargs
self._finished_slot = finished_slot
def run(self):
self._function(*self._args, **self._kwargs)
self._finished_slot()
return
def qt_thread_decorator(slot):
def decorator(function):
def wrapper(*args, **kwargs):
worker = Worker(function.__name__, slot, function, *args, **kwargs)
worker.start()
return
return wrapper
return decorator
我使用它的地方是:
import qtthreaddecorator
class MainWindow(QtGui.QMainWindow, form_class):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
self.init()
def init(self):
@qtthreaddecorator.qt_thread_decorator(self._fill_servers)
def _get_servers():
self._get_my_servers()
@qtthreaddecorator.qt_thread_decorator(self._fill_user_info)
def _get_user_info():
self._get_user_info()
_get_servers()
_get_user_info()
在我的情况下,_get_servers()
和 _get_user_info()
是顺序调用的,但我想让它们同时执行。
2 个回答
0
虽然这个问题的解决方案已经由Fenikso提供,但用装饰器来解决这个问题也很有意思。
我已经把我的qtthreaddecorator.py
修改成了以下内容:
from PyQt4 import QtCore
class Worker(QtCore.QThread):
threads = []
def __init__(self, thread_name, function, *args, **kwargs):
QtCore.QThread.__init__(self)
self._thread_name = thread_name
self._function = function
self._args = args
self._kwargs = kwargs
def run(self):
Worker.threads.append(self.currentThreadId())
self._function(*self._args, **self._kwargs)
self.emit(QtCore.SIGNAL('finished()'))
Worker.threads.remove(self.currentThreadId())
def qt_thread_decorator():
def decorator(function):
def wrapper(*args, **kwargs):
worker = Worker(function.__name__, function, *args, **kwargs)
def on_finish():
worker.quit()
worker.finished.connect(on_finish)
worker.start()
return worker
return wrapper
return decorator
然后在使用这个装饰器的代码中:
import qtthreaddecorator
class MainWindow(QtGui.QMainWindow, form_class):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
self.init()
def init(self):
@qtthreaddecorator.qt_thread_decorator()
def _get_servers():
self._get_my_servers()
@qtthreaddecorator.qt_thread_decorator()
def _get_user_info():
self._get_user_info()
_get_servers().finished.connect(self._fill_servers)
_get_user_info().finished.connect(self._fill_user_info)
1
我觉得你在使用装饰器的时候把事情搞得太复杂了。其实你只需要大约3到4行代码,就能轻松地把你的代码放到一个新线程里。而且,我认为你不应该直接从另一个线程里调用你完成的槽函数。你应该使用一个连接的信号来激活它。
import sys
from time import sleep
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class Signals(QObject):
update = pyqtSignal(int)
enable_button = pyqtSignal(bool)
class Window(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
self.button = QPushButton("Run", self)
self.button.clicked.connect(self.onButton)
self.progress = QProgressBar(self)
self.progress.setTextVisible(False)
self.layout = QVBoxLayout()
self.layout.setContentsMargins(5, 5, 5, 5)
self.layout.addWidget(self.button)
self.layout.addWidget(self.progress)
self.layout.addStretch()
self.worker_thread = QThread()
self.worker_thread.run = self.worker
self.worker_thread.should_close = False
self.signals = Signals()
self.signals.update.connect(self.progress.setValue)
self.signals.enable_button.connect(self.button.setEnabled)
self.setLayout(self.layout)
self.show()
self.resize(self.size().width(), 0)
# Override
def closeEvent(self, e):
self.worker_thread.should_close = True
self.worker_thread.wait()
@pyqtSlot()
def onButton(self):
self.button.setDisabled(True)
self.worker_thread.start()
# Worker thread, no direct GUI updates!
def worker(self):
for i in range(101):
if self.worker_thread.should_close:
break
self.signals.update.emit(i)
sleep(0.1)
self.signals.enable_button.emit(True)
app = QApplication(sys.argv)
win = Window()
sys.exit(app.exec_())