如何使用子进程或 QProcess 启动独立的 gnuplot 进程?

Posted

技术标签:

【中文标题】如何使用子进程或 QProcess 启动独立的 gnuplot 进程?【英文标题】:How to start an independent gnuplot process with either subprocess or QProcess? 【发布时间】:2021-06-17 09:59:30 【问题描述】:

已经有很多关于subprocessQProcess 的问题和答案,但我还没有找到我的问题的答案。所以,如果我在这批部分混乱或过时的答案中没有找到答案,请原谅。

这听起来很简单:在 PyQt GUI 应用程序中,我想启动一个独立的(子)进程。 在我的例子中:gnuplot,一个绘图工具,带有选项“-p”(持久,即绘图完成时保持窗口打开)。启动后,它应继续作为独立(交互式)应用程序运行。

文档 QProcesssubprocess 没有进一步帮助我。

代码:(我的精简示例,不能按预期工作)

from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QPushButton, QVBoxLayout
from PyQt5.QtCore import pyqtSlot, QProcess
import sys, subprocess

class App(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()
        
    def initUI(self):
        self.setGeometry(0,0,700,500)
        self.layout = QVBoxLayout()

        self.pb_plot_1 = QPushButton("Plot via subprocess")
        self.pb_plot_1.clicked.connect(self.on_click_pb_plot_1)
        self.layout.addWidget(self.pb_plot_1)

        self.pb_plot_2 = QPushButton("Plot via QProcess")
        self.pb_plot_2.clicked.connect(self.on_click_pb_plot_2)
        self.layout.addWidget(self.pb_plot_2)
        
        self.pb_plot_3 = QPushButton("Plot via QProcess and loading a file")
        self.pb_plot_3.clicked.connect(self.on_click_pb_plot_3)
        self.layout.addWidget(self.pb_plot_3)
        
        self.setLayout(self.layout) 
        self.show()

    @pyqtSlot()
    
    def get_plot_code(self):
        return '''
        # some gnuplot code, in reality much longer
        set term wxt
        plot sin(x)
        '''
        
    def on_click_pb_plot_1(self):
        gnuplotPath = r'gnuplot\bin\gnuplot'
        gnuplotCode = self.get_plot_code()
        proc = subprocess.Popen([gnuplotPath, '-p'], stdin=subprocess.PIPE)
        proc.communicate(gnuplotCode.encode())

    def on_click_pb_plot_2(self):
        gnuplotPath = r'gnuplot\bin\gnuplot'
        gnuplotCode = self.get_plot_code()
        proc = QProcess(self)
        proc.start(gnuplotPath, ["-p"])
        proc.write(gnuplotCode.encode())
     
    def on_click_pb_plot_3(self):
        gnuplotPath = r'gnuplot\bin\gnuplot'
        gnuplotFile = 'gnuplotFile.gp'
        proc = QProcess(self)
        proc.start(gnuplotPath, ["-p", gnuplotFile])
        
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

结果:

如果我按下按钮,每次都会打开 gnuplot 并显示图表。到目前为止,好的。 然而,

通过子进程: gnuplot 将作为交互式窗口工作,但我的 PyQt 应用程序将冻结(不响应),直到我关闭 gnuplot。 使用subprocess.Popen 似乎有close_fds=True 选项,它可能应该做我想要的,但在Windows 中,这似乎与通过stdin=subprocess.PIPE 发送命令一起使用。

请注意,在 Windows 上,您不能将 close_fds 设置为 true,并且 通过设置 stdin、stdout 或 stderr 重定向标准句柄

那么,我还能用subprocess 做些什么呢?添加proc.terminate() 将无济于事。

通过 QProcess: gnuplot 将冻结并且不能作为交互式窗口工作(无响应)。如果我杀死 gnuplot,我可以重新开始,但 gnuplot 会再次冻结。

我也尝试过类似startDetached() 等...但没有成功...

我可以想象这个问题可以通过代码中的微小变化来解决。但我不知道如何改变。 因此,我确信这是我对文档的有限理解,并且我缺少合适的示例。 我的配置:Win10、Python 3.6.3、PyQt 5.11.3

更新:(上面的代码已更改,添加了第 3 个变体)

通过 QProcess 并读取文件(而不是将代码写入 gnuplot)

a) gnuplot 将创建绘图,b) 什么都不会冻结,c) gnuplot 保持交互,d) 我可以独立关闭我的应用程序或 gnuplot。 但是,我不想从磁盘读取 gnuplot 文件,但我想将代码发送到 gnuplot(我假设是 stdin)。

更新 2:

也许这可能是对可以解释这一点的人的暗示。它似乎取决于 gnuplot 版本。以下结果:

5.2.0 QWindowsPipeWriter::write failed. (The handle is invalid.)
5.2.2 QWindowsPipeWriter::write failed. (The handle is invalid.)
5.2.3 QWindowsPipeWriter::write failed. (The handle is invalid.)
5.2.4 QWindowsPipeWriter::write failed. (The handle is invalid.)
5.2.5 QWindowsPipeWriter::write failed. (The handle is invalid.)

5.2.6 works
5.2.7 works
5.2.8 works

5.4.0 freezing
5.4.1 freezing

【问题讨论】:

我没有观察到它在 Linux、python 3.9.5 和 PyQt5 5.15.4 中使用 QProcess 或子进程冻结 @eyllanesc 感谢您的检查。嗯,又是一个烦人的 Windows 特例? 您的指示对我来说似乎很奇怪,因为 QProcess 不应该阻止任何东西,您提供的代码是生成冻结的代码还是您使用了其他代码? @eyllanesc 我使用的正是这段代码。那么,这可能是 Windows 下的 gnuplot 特定“问题”吗?我将修改答案并添加另一个观察结果。 嗯,QProcess 不应该阻止任何东西,所以我觉得很奇怪。尝试更新版本的 PyQt5,因为它可能是一个错误 【参考方案1】:

好的,@eyllanesc 是对的...我将 PyQt5 从 5.11.3 更新到 5.15.4,问题似乎不再出现。无论这是什么错误或不兼容...

【讨论】:

以上是关于如何使用子进程或 QProcess 启动独立的 gnuplot 进程?的主要内容,如果未能解决你的问题,请参考以下文章

如何关闭 Qt 子进程并让子进程执行清理代码?

如何使用 QProcess 等待子进程?

Qt使用QProcess进程间双向通信(linux和win系统)

如何确定在 Linux 上使用 Qt4 终止 QProcess 的信号?

QProcess 无法启动进程:未找到进程

QProcess 终止进程树