Windows 上的 Python 子进程:启动子进程“cmd.exe”并为其提供 bat 文件,停止主进程执行
Posted
技术标签:
【中文标题】Windows 上的 Python 子进程:启动子进程“cmd.exe”并为其提供 bat 文件,停止主进程执行【英文标题】:Python subprocess on Windows: Launching subprocess "cmd.exe" and giving it a bat-file, stops the main process execution 【发布时间】:2020-01-01 21:31:18 【问题描述】:我打算使用 Python 中的 subprocess
模块运行带有 cmd.exe
的 Windows .bat
。为简单起见,我创建了一个可重现的最小示例,您可以在自己的计算机上运行该示例。
首先,我将展示它的设置。然后你会得到代码,然后我解释出了什么问题。
1。设置
我有一台安装了Python 3.7
和PyQt5
的Windows 10 计算机。对于可重现的示例,我创建了一个包含三个文件的文件夹:
文件 my_subprocess.py 是一个 python 脚本,它生成一个带有显示计数器标签的窗口。 my_subprocess.bat 文件是一个非常简单的 bat 文件,它执行这个 python 文件。
my_mainprocess.py 文件使用 Python subprocess
模块来运行 cmd.exe
中的 bat 文件。子进程启动后,一直监听子进程的输出通道,并在窗口中显示输出:
2。代码
my_subprocess.py:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class SubprocessWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 600, 300)
self.setWindowTitle("MY SUBPROCESS WINDOW")
# 1. OUTER FRAME
self.__frm = QFrame()
self.__frm.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.__frm.setStyleSheet("QFrame background-color:#eeeeec; border-color: #2e3436; ")
self.__lyt = QVBoxLayout()
self.__frm.setLayout(self.__lyt)
self.setCentralWidget(self.__frm)
self.show()
# 2. WIDGETS
self.__cntr = 0
self.__myLabel = QLabel(f"cntr: self.__cntr")
self.__myLabel.setFixedHeight(40)
self.__myLabel.setFixedWidth(200)
self.__myLabel.setStyleSheet("QLabel background-color:#ffffff; border-color: #2e3436; border-width: 1px; border-style: solid; font: 16pt; ")
self.__lyt.addWidget(self.__myLabel)
self.count()
return
def count(self):
self.__cntr += 1
self.__myLabel.setText(f"cntr: self.__cntr")
print(f"cntr: self.__cntr")
QTimer.singleShot(500, self.count)
return
if __name__== '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Plastique'))
myGUI = SubprocessWindow()
sys.exit(app.exec_())
my_subprocess.bat:
@echo off
ECHO Start Subprocess
SETLOCAL
python my_subprocess.py
ECHO Finish Subprocess
my_mainprocess.py:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys, os, subprocess
class MainprocessWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(100, 500, 600, 300)
self.setWindowTitle("MY MAIN PROCESS WINDOW")
# 1. OUTER FRAME
self.__frm = QFrame()
self.__frm.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.__frm.setStyleSheet("QFrame background-color:#fce94f; border-color: #2e3436; ")
self.__lyt = QVBoxLayout()
self.__frm.setLayout(self.__lyt)
self.setCentralWidget(self.__frm)
self.show()
# 2. WIDGETS
self.__cntr = 0
self.__myTextEdit = QPlainTextEdit()
self.__myTextEdit.setStyleSheet("""
QPlainTextEdit
color: #ffffffff;
font: 12pt;
background: #ff000000;
border-width: 1px;
border-color: #ff888a85;
border-style: solid;
border-radius: 2px;
padding: 1px;
margin: 5px 5px 5px 5px;
""")
self.__myTextEdit.setFixedHeight(290)
self.__myTextEdit.setFixedWidth(590)
self.__lyt.addWidget(self.__myTextEdit)
self.__proc = None
QTimer.singleShot(1000, self.start_subprocess)
return
def start_subprocess(self):
self.activateWindow()
self.__proc = subprocess.Popen("cmd.exe", shell=True, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
command = "my_subprocess.bat\n"
self.__proc.stdin.write(command)
self.__proc.stdin.flush()
QTimer.singleShot(100, self.catch_output)
return
def catch_output(self):
if self.__proc.poll() is None:
self.__proc.stdout.flush()
output_msg = self.__proc.stdout.readline()
self.__myTextEdit.appendPlainText(output_msg)
else:
self.__myTextEdit.appendPlainText("Subprocess dead")
QTimer.singleShot(300, self.catch_output)
return
if __name__== '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Plastique'))
myGUI = MainprocessWindow()
sys.exit(app.exec_())
3。运行代码
请将代码复制粘贴到文件 my_subprocess.py、my_subprocess.bat 和 my_mainprocess.py 中。打开控制台,然后导航到包含这些文件的文件夹。运行python my_mainprocess.py
启动主进程,然后启动子进程。您应该会看到顶部显示的两个窗口:第一个属于子进程,第二个属于主进程。
4。问题
4.1 主进程挂起
我相信主进程和子进程都应该同时运行。看主进程的代码。任何地方都没有阻塞呼叫。它只是捕获stdout
通道上的输出并将其打印在窗口上:
def catch_output(self):
if self.__proc.poll() is None:
self.__proc.stdout.flush()
output_msg = self.__proc.stdout.readline()
self.__myTextEdit.appendPlainText(output_msg)
else:
self.__myTextEdit.appendPlainText("Subprocess dead")
QTimer.singleShot(300, self.catch_output)
return
但是,主进程会挂起,直到您停止子进程(通过关闭子进程窗口)。然后,主进程继续并打印所有输出。为什么?
4.2 主进程崩溃
事情变得更糟了。主进程完成标准输出通道的输出后,就崩溃了。
我相信我知道为什么 - 但我没有解决方案。如果子进程处于活动状态,则调用 self.__proc.poll()
应该返回 None
,否则返回一个数字。相反,我注意到它只是不断返回None
,即使子进程已经死了并且很久以前就被埋没了。
如何规避这个问题?
更新:
在每个打印语句之后的my_subprocess.py
代码中添加sys.stdout.flush()
肯定会有所帮助。但这不是一个好的解决方案,因为my_subprocess.py
只是任何可以作为子进程运行的潜在程序的代表。对于大多数此类程序,我们无权访问源代码。
换句话说,解决方案应该只关注my_mainprocess.py
可以做的事情。
【问题讨论】:
【参考方案1】:将MainprocessWindow.start_subprocess
更改为:
def start_subprocess(self):
self.activateWindow()
command = "cmd.exe /c my_subprocess.bat\n"
self.__proc = subprocess.Popen(command, shell=True, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
QTimer.singleShot(100, self.catch_output)
return
并将行 sys.stdout.fulsh()
添加到 SubprocessWindow.count
,如下所示:
def count(self):
self.__cntr += 1
self.__myLabel.setText(f"cntr: self.__cntr")
print(f"cntr: self.__cntr")
sys.stdout.flush()
QTimer.singleShot(500, self.count)
return
【讨论】:
嗨@Matic,您关于以不同方式启动子流程的建议肯定很有帮助。但是,我不能将sys.stdout.flush()
添加到子流程代码本身。这是因为这个子进程代表了任何可以作为子进程运行的潜在程序。我忘了在我的问题中提到这一点。以上是关于Windows 上的 Python 子进程:启动子进程“cmd.exe”并为其提供 bat 文件,停止主进程执行的主要内容,如果未能解决你的问题,请参考以下文章
在 Windows 上的 Python Popen 子进程中暂停 FFmpeg 编码
如何通过 python 子进程与 mac 上的应用程序交互?