【问题标题】:PyQt, QThread, GIL, GUIPyQt、QThread、GIL、GUI
【发布时间】:2017-06-24 11:33:59
【问题描述】:

我有用 Python 编写的 GUI 和程序逻辑。我经常通过调用urllib.requests(等等)从网络请求信息,当GUI 无响应但此调用使用QThread 包装时,这会导致问题。我认为这是因为GIL。但是当我可以在 PyQt 应用程序中使用 QThread 时,如果我不能使代码异步工作,那么在 PyQt 中使用它有什么用呢?

--代码--

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() 按顺序调用,但我想同时执行它们。

【问题讨论】:

  • 你是否正确启动线程,例如使用worker_thread.start()?因为如果您执行worker_thread.run(),它将运行。只是不在另一个线程中。
  • 正是worker_thread.start()。等等,我要发布代码。
  • 装饰器的有趣使用...您介意创建一个可运行的小示例,按钮在另一个线程中运行一些睡眠吗?或者我可以用一个工作线程示例来回答你的问题,但我不确定它是否会按照书面形式回答你的问题。
  • 我同意@Fenikso,绝对是装饰器的有趣用途。我认为您的问题可能源于wrapper 没有返回工作人员,因此它不存在于该函数之外。
  • 仅仅从wrapper返回是不够的。最终,您需要工作人员存在于您正在装饰的功能之外,否则当这些功能返回时,您最终会遇到同样的问题。我认为您使用装饰器使事情变得过于复杂。

标签: python pyqt qthread gil


【解决方案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_())

【讨论】:

  • 我已经解决了装饰器的问题,但它不起作用,但是是的,使用装饰器可能有点复杂。另一个问题 - 更新 GUI(我的代码中没有发布)通过连接到我由装饰器创建的线程的finished 信号来解决。对于想知道如何使用装饰器使方法同时工作的其他人,我将用解决方案更新我的问题。
  • 我在您的代码中看不到信号。我只看到self._finished_slot()
  • @VictorPolevoy 不要用修复更新问题。写一个答案。
【解决方案2】:

虽然 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)

【讨论】:

  • 但是我还在想worker.quit() 什么时候会真正销毁线程——在通知每个finished()-订阅者之后?会不会造成问题?
  • 我建议对信号使用新的符号。我认为旧的在 PyQt5 中被删除了。
猜你喜欢
  • 2018-01-21
  • 2017-01-29
  • 2015-09-08
  • 2014-03-25
  • 2014-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多