【问题标题】:What's the correct pattern for threading in a python qt5 application?python qt5应用程序中线程的正确模式是什么?
【发布时间】:2017-03-25 03:51:29
【问题描述】:

我正在尝试编写一个运行时间很长但不是 CPU 密集型进程的 pyqt5 应用程序。我希望能够在不挂起 UI 的情况下运行它,所以我正在尝试使用线程,但是因为我似乎不能只运行一个线程并在它通过它的代码后让它停止,这样它可以再次运行,我已经尝试设置线程等待变量在运行前更改。

我知道这不是在 pyqt 应用程序中运行长进程的正确模式。

import time
import threading
from PyQt5 import QtWidgets, uic


class MyApp(QtWidgets.QMainWindow):
    _run_thread = False

    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        self.ui = uic.loadUi('myapp.ui', self)

        self.ui.start_thread_button.clicked.connect(self._run_thread_function)

        self._thread = threading.Thread(target=self._run_thread_callback)
        self._thread.daemon = True
        self._thread.start()

        self.ui.show()

    def _run_thread_callback(self):
        while True:
            if self._run_thread:
                print("running thread code...")
                time.sleep(10)
                print("thread code finished")
                self._run_thread = False

    def _run_thread_function(self):
        print("starting thread...")
        self._run_thread = True


def main():
    app = QtWidgets.QApplication(sys.argv)
    MyApp()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

【问题讨论】:

  • 好吧,你会遇到问题,因为你有一个名为self._run_thread 的方法和一个同名的实例属性。在某些时候,您会用布尔值覆盖您的方法。但不管怎样,您可能应该切换到 QThreads。看看stackoverflow.com/documentation/pyqt/2775/…,它应该很容易适应 PyQt5。页面底部还有一些关于线程安全的准则。
  • 好的,我已经更改了函数的名称,但这只是一个示例,并不是我问题的重点。因此,从我听到的消息来看,原生 python threading 模块不适合与 Qt 一起使用。对于任何 UI 应用程序都是这样吗?例如,如果我从 Qt 切换到 Tkinter,我可以使用 threading 模块吗?
  • 我成功地将threading 模块与Qt 一起使用,但您不应该这样做(see here)。如果你想从你的线程与 GUI 交互,你真的应该使用 QThreads 和信号/插槽(因为禁止从线程直接访问 GUI 对象)。但是,如果您真的想使用 python 线程,使用QApplication.postEvent 在辅助线程和主线程之间进行通信似乎是安全的。我有一个基于这个想法的项目here(目前仅限 PyQt4

标签: python multithreading pyqt pyqt5 python-multithreading


【解决方案1】:

下面是一个简单的演示,展示了如何启动和停止工作线程,以及如何安全地与 gui 线程通信。

import sys
from PyQt5 import QtCore, QtWidgets

class Worker(QtCore.QThread):
    dataSent = QtCore.pyqtSignal(dict)

    def __init__(self, parent=None):
        super(Worker, self).__init__(parent)
        self._stopped = True
        self._mutex = QtCore.QMutex()

    def stop(self):
        self._mutex.lock()
        self._stopped = True
        self._mutex.unlock()

    def run(self):
        self._stopped = False
        for count in range(10):
            if self._stopped:
                break
            self.sleep(1)
            data = {
                'message':'running %d [%d]' % (
                    count, QtCore.QThread.currentThreadId()),
                'time': QtCore.QTime.currentTime(),
                'items': [1, 2, 3],
                }
            self.dataSent.emit(data)

class Window(QtWidgets.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.edit = QtWidgets.QPlainTextEdit()
        self.edit.setReadOnly(True)
        self.button = QtWidgets.QPushButton('Start')
        self.button.clicked.connect(self.handleButton)
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.edit)
        layout.addWidget(self.button)
        self._worker = Worker()
        self._worker.started.connect(self.handleThreadStarted)
        self._worker.finished.connect(self.handleThreadFinished)
        self._worker.dataSent.connect(self.handleDataSent)

    def handleThreadStarted(self):
        self.edit.clear()
        self.button.setText('Stop')
        self.edit.appendPlainText('started')

    def handleThreadFinished(self):
        self.button.setText('Start')
        self.edit.appendPlainText('stopped')

    def handleDataSent(self, data):
        self.edit.appendPlainText('message [%d]' %
            QtCore.QThread.currentThreadId())
        self.edit.appendPlainText(data['message'])
        self.edit.appendPlainText(data['time'].toString())
        self.edit.appendPlainText(repr(data['items']))

    def handleButton(self):
        if self._worker.isRunning():
            self._worker.stop()
        else:
            self._worker.start()

if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 100, 400, 400)
    window.show()
    sys.exit(app.exec_())

【讨论】:

  • 是否可以从Worker 中直接操作 UI 元素?我想从一个线程中操作几个元素。我是否必须从线程发送一个 json 字符串,然后在更改 UI 之前在窗口中解析它?
  • @waspinator。在主线程之外直接操作 gui 不是线程安全的。不用json:发个普通的pythondict就可以了,可以包含其他复杂类型(但显然不是gui元素)。我更新了我的示例,以便工作人员将dict 发送回主线程。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-21
  • 1970-01-01
  • 2015-10-27
  • 2013-06-29
相关资源
最近更新 更多