使用 QProcess 通过 PyQt 运行和监控系统进程

Posted

技术标签:

【中文标题】使用 QProcess 通过 PyQt 运行和监控系统进程【英文标题】:Use of QProcess to run and monitor system processes with PyQt 【发布时间】:2017-02-19 14:55:17 【问题描述】:

我需要一个非阻塞式 GUI 解决方案来运行未定义数量的系统命令(例如将一些参数作为输入的 bash 脚本),monitor 他们的地位。 (例如:运行/完成)和终止(杀死)进程。

一个例子可以是:

    从列表中选择一个应用程序 (QComboBox)

    设置参数(QLineEdit)

    运行它(QProcess)

当它运行时,追加:

命令 参数 状态

作为 QTableWidget 中的行

..我正在寻找一种解决方案来监控每个命令的状态。

应用程序可以是这样一个简单的脚本:

class runcommands(QWidget):
    def __init__(self, parent=None):
        super(runcommands, self).__init__(parent)

        layout = QFormLayout()
        self.commandlist = QComboBox()
        self.param = QLineEdit()
        self.runit = QToolButton()
        self.runit.setText('run')
        self.runit.clicked.connect(self.runcommand)
        self.commandlist.addItems(['simplerun.py', 'simplerun2.py'])
        self.table = QTableWidget()
        self.table.setColumnCount(5)
        self.model = QStandardItemModel()
        self.table.setHorizontalHeaderLabels(['Process', 'Parameter', 'STDOut', 'Status', 'Kill'])
        self.rowcount = 0

        layout.addRow(self.commandlist)
        layout.addRow(self.param)
        layout.addRow(self.runit)

        layout.addRow(self.table)

        self.setLayout(layout)
        self.setWindowTitle("Run & Monitor")
        self.commandrunning=0
        self.mylistofprocesses=[]



    def runcommand(self):
        # add a record in the QTableWidget
        # updating its row number at each run
        self.rowcount = self.rowcount + 1
        self.table.setRowCount(self.rowcount)

        # add column 0: command string
        self.c1 = QTableWidgetItem()
        self.c1.setText("%s" % os.path.join(os.getcwd(), self.commandlist.currentText()))
        self.table.setItem(self.rowcount - 1, 0, self.c1)

        # add column 1: parameter string
        self.c2 = QTableWidgetItem()
        self.c2.setText("%s" % self.param.text())
        self.table.setItem(self.rowcount - 1, 1, self.c2)

        # add column 2 to store the  Process StandardOutput
        stdout_item = QTableWidgetItem()
        self.table.setItem(self.rowcount - 1, 2, stdout_item)

        # add column 3: index to store the process status (0: Not Running, 1: Starting, 2: Running)
        status_item = QTableWidgetItem()
        self.table.setItem(self.rowcount - 1, 3, status_item)

        # add column 4: kill button to kill the relative process
        killbtn = QPushButton(self.table)
        killbtn.setText('Kill')
        self.table.setCellWidget(self.rowcount - 1, 4, killbtn)

        # Initiate a QProcess running a system command
        process = QtCore.QProcess()
        command = 'python3' + ' ' + os.getcwd() + '/' + self.commandlist.currentText() + ' ' + self.param.text()
        process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
        # connect the stdout_item to the Process StandardOutput
        # it gets constantly update as the process emit std output
        process.readyReadStandardOutput.connect(lambda: stdout_item.setText(str(process.readAllStandardOutput().data().decode('utf-8'))))
        # start the process
        process.start(command)
        # this was supposed to add the process status in the relative column ... BUT it DOESN'T do it
        status_item.setText(str(process.ProcessState()))
        # connect the kill button to the process.kill method, to stop the process
        killbtn.clicked.connect(process.kill)
        killbtn.clicked.connect(lambda: killbtn.setText('Killed'))
        # this was supposed to 'UPDATE' the process status (from running to stoppted) in the relative column ... BUT it DOESN'T do it
        killbtn.clicked.connect(lambda: status_item.setText(str(process.ProcessState())))
        # append the process to a list so that it doesn't get destroyed at each run
        self.mylistofprocesses.append(process)


def main():
    app = QApplication(sys.argv)
    ex = runcommands()
    ex.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

感谢 Avaris 在 IRC 上的帮助,我解决了将表中的每一行连接到单独进程的主要问题。

在对原始问题进行了一些编辑后,我清理了一些代码,并且能够添加一个按钮来停止/终止进程。

为了完成这个示例任务,我需要实现对所有活动进程的监控,并在第 4 列的表中“实时”更新它们的 状态(除了打印标准输出第三列)。

我尝试这样做:

status_item.setText(str(process.ProcessState())))

但我无法让它工作。

【问题讨论】:

这段代码的问题是每次运行新命令时进程都会被替换(销毁)。正如 Avaris 的 PyQt IRC 所建议的那样,解决方案是将进程附加到一个列表中,这样就不会被破坏。 我将代码更新为工作版本。现在该表将接收添加到进程列表中的每个命令的更新。写作仍未完成,因为我需要在此示例中添加进程的“状态”(正在运行/已完成),而不是标准输出。我还需要为每一行添加一个新列,其中包含一个小部件来“停止/终止”进程。 【参考方案1】:

您必须使用stateChanged 信号:

[...]
self.mylistofprocesses.append(process)

        status = QProcess.NotRunning: "Not Running",
                  QProcess.Starting: "Starting",
                  QProcess.Running: "Running"

        process.stateChanged.connect(lambda state: status_item.setText(status[state]))

截图:

【讨论】:

感谢 eyllanesc! process.stateChanged 正是我所缺少的!谢谢。 我还为按钮添加了更新,以便更新其文本并在进程重新启动时禁用它:process.stateChanged.connect(lambda state: killbtn.setText('Terminated') if status[state]=="Not Running" else killbtn.setText('Kill')) 和:process.stateChanged.connect(lambda state: killbtn.setEnabled(False) if status[state]=="Not Running" else killbtn.setText('Kill')) 它有帮助,我还在努力。为了完成这个示例任务,我正在寻找一种方法来清除具有终止进程的行中的表。我将在问题中添加此方法的代码..它不能完全按预期工作。 每次你在 SO 中提出一个具体的问题。您的问题涉及很多方面。 另外,你要求你没有放置的是你的问题。如果您需要帮助,请在另一个问题中进行。

以上是关于使用 QProcess 通过 PyQt 运行和监控系统进程的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 3 和 PyQt 中使用 QProcess.finished()

Pyqt - 无法从 QProcess 获得任何输出

PyQt:多个 QProcess 和输出

监控正在运行的 qprocess 并在 qprocess 完成时返回值

QProcess正常退出

冻结后使用 QProcess 运行 Python 脚本