【问题标题】:subprocess cause user interface to freeze子进程导致用户界面冻结
【发布时间】:2013-12-09 13:17:04
【问题描述】:

当点击按钮时,以下代码开始运行(从主(GUI)线程调用),然后用户界面被冻结

def on_pushButton_clicked(self):
        subprocess.call([r'C:\Program Files\ffmpeg-20131122-git-fb7d70c-win32-static\bin\ffmpeg', '-f', 'concat', '-i','mylist.txt', '-c',  'copy', 'output.mp4'])

任何人都可以解释为什么? subprocess() 本身不能异步运行?

【问题讨论】:

  • 正如@falsetu 所说,使用 Popen 你可以看到一个例子here
  • @Kobi K 那么如果我想知道这个过程什么时候完成应该怎么办?
  • Popen.communicate() 等待进程终止,完成后下一行将执行stdOutValue, stdErrValue = communicateRes,然后您将获得标准输出和错误。
  • 当我使用 Popen.communicate() 时,用户界面再次冻结。
  • 是的,你是对的,看here这是使用poll()的解决方案,你正在调用一个子进程,通过使用pool()你可以检查子进程是否已经终止。

标签: python pyqt subprocess pyside


【解决方案1】:

subprocess.call 一直等到子进程终止。请改用subprocess.Popen

def on_pushButton_clicked(self):
    subprocess.Popen([r'C:\Program Files\ffmpeg-20131122-git-fb7d70c-win32-static\bin\ffmpeg', '-f', 'concat', '-i','mylist.txt', '-c',  'copy', 'output.mp4'])

【讨论】:

  • 如果我想知道进程何时结束,我该怎么办?
  • @user1485853, subprocess.Popen 返回一个Popen 对象;把它保存在某个地方。然后使用subprocess.Popen.poll 方法检查进程是否终止。 (如果进程仍在运行,则返回None。否则返回返回状态)
  • @user1485853,是的,定期检查。或者,您可以在单独的线程中运行 subprocess.call
  • @user1485853,更可取的是您可以使用 GUI 工具包提供的子进程功能(可能提供进程终止事件。)例如,请参阅 pyqt.sourceforge.net/Docs/PyQt4/qprocess.html#finished
【解决方案2】:

正如其他人所说,subprocess.call 将等到命令完成。

但鉴于您使用的是 PyQt,使用 QProcess 可能会更好,因为这将允许您使用信号和事件来保持 GUI 响应。

有几个问题需要考虑。

首先,如果输出文件已经存在,示例ffmpeg 命令将挂起,因为默认情况下,它会提示用户允许覆盖。所以最好添加-y-n 标志来处理这个问题。

其次,该命令也可能由于某些意外原因而挂起。所以你可能应该给用户一种强制终止进程的方法。

最后,如果用户试图在命令完成之前关闭应用程序会发生什么?可能你需要处理主窗口的closeEvent 来处理它。

下面的演示脚本显示了处理上述问题的一些可能方法:

from PyQt4 import QtCore, QtGui

class Window(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        layout = QtGui.QVBoxLayout(self)
        self.label = QtGui.QLabel('Elapsed: 0', self)
        layout.addWidget(self.label)
        self.buttonStart = QtGui.QPushButton('Start', self)
        self.buttonStart.clicked.connect(self.handleButtonStart)
        layout.addWidget(self.buttonStart)
        self.buttonStop = QtGui.QPushButton('Stop', self)
        self.buttonStop.setDisabled(True)
        self.buttonStop.clicked.connect(self.handleButtonStop)
        layout.addWidget(self.buttonStop)
        self._process = QtCore.QProcess(self)
        self._process.started.connect(self.handleStarted)
        self._process.finished.connect(self.handleFinished)
        self._process.error.connect(self.handleError)
        self._time = QtCore.QTime()
        self._timer = QtCore.QTimer(self)
        self._timer.timeout.connect(self.handleTimeout)

    def closeEvent(self, event):
        if self._timer.isActive():
            event.ignore()
        else:
            QtGui.QWidget.closeEvent(self, event)

    def handleButtonStart(self):
        self._running = True
        self._process.start('ffmpeg', [
            '-f', 'concat', '-i', 'input.txt',
            '-c',  'copy', '-y', 'output.mp4',
            ], QtCore.QIODevice.ReadOnly)

    def handleTimeout(self):
        self.label.setText(
            'Elapsed: %.*f' % (2, self._time.elapsed() / 1000.0))

    def handleButtonStop(self):
        if self._timer.isActive():
            self._process.close()

    def handleStarted(self):
        self.buttonStart.setDisabled(True)
        self.buttonStop.setDisabled(False)
        self._time.start()
        self._timer.start(50)

    def handleFinished(self):
        self._timer.stop()
        self.buttonStart.setDisabled(False)
        self.buttonStop.setDisabled(True)

    def handleError(self, error):
        if error == QtCore.QProcess.CrashExit:
            print('Process killed')
        else:
            print(self._process.errorString())

if __name__ == '__main__':

    import sys
    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 200, 100)
    window.show()
    sys.exit(app.exec_())

【讨论】:

  • 我们为什么要在这里偶尔调用 qApp.processEvents() ?
  • 我测试了即使我关闭了应用程序,但是ffmpeg命令仍然在后台运行直到它完成,你能解释一下为什么吗?也许我们可以依赖这个特性所以不需要处理closeEvent .
  • @user1485853。你是对的 - qApp.processEvents() 行来自较早的编辑,应该被删除(现在已经消失了)。
  • @user1485853。如果 ffmpeg 仍在运行,关闭窗口只会隐藏您的应用程序,直到命令完成(如果您查看进程查看器,您可以看到这一点)。但是如果 ffmpeg 由于某种原因挂起,您的应用程序将继续运行而无法关闭它。为了避免这种情况,许多应用程序会在 closeEvent 中显示一个消息框,让用户选择下一步该做什么(或者更粗略地说,只是防止应用程序被隐藏,就像我的脚本一样)。
  • 如何防止应用程序隐藏在此脚本中?
猜你喜欢
  • 2016-07-22
  • 2017-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-08
  • 2018-05-04
相关资源
最近更新 更多