【问题标题】:Update widget with data from another Python process, without blocking it使用来自另一个 Python 进程的数据更新小部件,而不阻塞它
【发布时间】:2019-04-11 01:50:47
【问题描述】:

我正在运行一个 Python 脚本来提取和本地化一些文件。我想使用QDialog 通过QProgressBar 显示进度状态,以及正在复制的文件列表。

首先让我说本地化脚本不能集成到 PyQt 脚本中 - 否则我知道解决方案将非常简单。我需要将本地化脚本与 UI 分开,并让它们同时运行。

我曾考虑通过一个线程从本地化脚本运行 UI,以避免它阻塞本地化进程。但那时我不知道如何更新 UI 元素,因为我没有可以调用和更新的实例,因为我已经用线程启动了它。

这是一个简化的对话框代码示例:

from PyQt5 import QtCore, QtWidgets
import sys

class Ui_dialog_main(object):

    def setupUi(self, dialog_main):
        dialog_main.setWindowTitle("Test")
        dialog_main.resize(390, 120)

        self.progress_bar = QtWidgets.QProgressBar(dialog_main)
        self.progress_bar.setGeometry(QtCore.QRect(10, 60, 371, 30))

        self.label_localizing = QtWidgets.QLabel(dialog_main)
        self.label_localizing.setGeometry(QtCore.QRect(10, 10, 81, 25))
        self.label_localizing.setText("Package:")

        QtCore.QMetaObject.connectSlotsByName(dialog_main)


def start():
    app = QtWidgets.QApplication(sys.argv)
    dialog_main = QtWidgets.QDialog()
    ui = Ui_dialog_main()
    ui.setupUi(dialog_main)
    dialog_main.show()
    sys.exit(app.exec_())

这就是我在定位器文件中启动线程的方式:

thread = Thread(target=LocManager.start)
thread.start()

其中LocManager 是 ui .py 文件的名称。

当然,这样主脚本不会被 ui 卡住,这是我的目标之一 - 但我不知道在这种情况下如何更新进度条和标签。我发现了几个讨论类似问题的线程,但没有什么对我有帮助。

我希望我的描述足够清楚。

更新:

我找到了解决这个here 的方法,使用管道。即使我不确定这是解决此问题的适当方法,但它确实起到了作用。如链接中所述,我没有使用两个 PyQt GUI 之间的“双向通信”,而是修改了代码以在我的 GUI 和本地化脚本之间进行双向通信。

【问题讨论】:

  • thread = Thread(target=LocManager.start) 中,目标应该是函数的名称,而不是.py 文件
  • @nathancy 它确实是一个函数的名称。这是我在第一块代码中定义的'def start():'。顺便说一句,该代码正在运行。我的问题是在线程运行后更新我用这些行创建的 UI。
  • @Izzy88 如我的回答所示,问题是您在另一个线程中运行 GUI,这是被禁止的,您必须执行相反的操作:复制任务必须在另一个线程中执行,如果您提供minimal reproducible example,我们可以为您提供更多帮助
  • @eyllanesc 谢谢你的回答。不幸的是,复制任务在其他所有事情之前开始,没有其他办法。我只是希望有一个不涉及更改调用我的流程的逻辑/顺序的解决方案。因此,给出额外的最小示例没有任何意义,因为最小示例已经存在。正如你所说,我试图做的事情是不允许的,所以我会坚持文档。谢谢
  • @Izzy88 没错,不幸的是,你将不得不改变你的逻辑。

标签: python multithreading pyqt5 ipc qdialog


【解决方案1】:

解决此问题的一种方法是在单独的进程中运行对话框,然后使用某种形式的IPC 发送更新。下面的解决方案使用 Qt 的 QLocalServerQLocalSocket 类将用 json 编码的 dict 传递给对话进程。每当接收到新数据时,服务器都会发出一个信号,对话框连接到该信号以处理更新。当发送进程退出时,服务器进程自动关闭。

test.py

import time
from dlg_server import send_data

for message in 'One Two Three Four Five'.split():
    send_data(message=message)
    time.sleep(2)

dlg_server.py

import sys, os, json, atexit
from PyQt5 import QtCore, QtWidgets, QtNetwork

SERVER = 'dlg_server'
_tries = 0

def send_data(**data):
    socket = QtNetwork.QLocalSocket()
    socket.connectToServer(SERVER, QtCore.QIODevice.WriteOnly)
    if socket.waitForConnected(500):
        socket.write(json.dumps(data).encode('utf-8'))
        if not socket.waitForBytesWritten(2000):
            raise RuntimeError('could not write to socket: %s' %
                  socket.errorString())
        socket.disconnectFromServer()
    elif socket.error() == QtNetwork.QAbstractSocket.HostNotFoundError:
        global _tries
        if _tries < 10:
            if not _tries:
                if QtCore.QProcess.startDetached(
                    'python', [os.path.abspath(__file__)]):
                    atexit.register(lambda: send_data(shutdown=True))
                else:
                    raise RuntimeError('could not start dialog server')
            _tries += 1
            QtCore.QThread.msleep(100)
            send_data(**data)
        else:
            raise RuntimeError('could not connect to server: %s' %
                socket.errorString())
    else:
        raise RuntimeError('could not send data: %s' % socket.errorString())


class Server(QtNetwork.QLocalServer):
    dataReceived = QtCore.pyqtSignal(object)

    def __init__(self):
        super().__init__()
        self.newConnection.connect(self.handleConnection)
        if not self.listen(SERVER):
            raise RuntimeError(self.errorString())

    def handleConnection(self):
        data = {}
        socket = self.nextPendingConnection()
        if socket is not None:
            if socket.waitForReadyRead(2000):
                data = json.loads(str(socket.readAll().data(), 'utf-8'))
                socket.disconnectFromServer()
            socket.deleteLater()
        if 'shutdown' in data:
            self.close()
            self.removeServer(self.fullServerName())
            QtWidgets.qApp.quit()
        else:
            self.dataReceived.emit(data)


class Dialog(QtWidgets.QDialog):
    def __init__(self):
        super().__init__()
        self.setGeometry(50, 50, 200, 30)
        layout = QtWidgets.QVBoxLayout(self)
        self.label = QtWidgets.QLabel()
        layout.addWidget(self.label)

    def handleDataReceived(self, data):
        self.show()
        self.label.setText(data.get('message', ''))

if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    dialog = Dialog()
    server = Server()
    server.dataReceived.connect(dialog.handleDataReceived)
    app.exec_()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-14
    • 1970-01-01
    • 2019-04-17
    • 1970-01-01
    • 2021-05-01
    • 1970-01-01
    • 2020-03-09
    相关资源
    最近更新 更多