如何在 Windows 上崩溃后保持 subprocess.Popen 控制台打开?
How to keep subprocess.Popen console open after crashing on Windows?
我的目标是为每个 Popen
进程使用几个不同的参数调用主程序,每个进程都有自己的控制台 window。然而,一旦遇到崩溃,它就会关闭那个控制台,我真的很想让它保持打开状态。
import subprocess
from subprocess import CREATE_NEW_CONSOLE
import time
for i in range(1, 5):
subprocess.Popen(["python", "main.py", str(i), str(i)], close_fds=False, creationflags=CREATE_NEW_CONSOLE)
time.sleep(3)
是否可以使用 subprocess.Popen
创建一个新进程并保持控制台打开以便我读取错误?
Link 到 subprocess docs.
控制台 window 由 conhost.exe (Win 7+) 的实例托管,当没有进程附加到它时退出。所以你只需要将第二个 python.exe 进程附加到每个控制台并让它等待。这是一个简单的演示脚本,它使等待进程等待主进程(即工作进程的父进程):
import sys
# If started with a pid, wait for the associated process to exit.
if len(sys.argv) > 1:
import ctypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
SYNCHRONIZE = 0x00100000
pid = int(sys.argv[1])
hproc = kernel32.OpenProcess(SYNCHRONIZE, 0, pid)
if not hproc:
sys.exit(ctypes.get_last_error())
print('waiter: waiting')
kernel32.WaitForSingleObject(hproc, -1)
sys.exit(0)
import os
import subprocess
print('worker: start')
# The waiter waits on our parent (ppid).
p = subprocess.Popen([sys.executable, sys.argv[0], str(os.getppid())])
print('worker: exit')
例如:
>>> import sys, subprocess
>>> flags = subprocess.CREATE_NEW_CONSOLE
>>> p = subprocess.Popen([sys.executable, 'main.py'], creationflags=flags)
>>> p.wait()
0
>>> exit()
工作进程重新生成自身以等待其父进程。然后它以 return 代码 0 退出。控制台 window 在 exit()
后关闭。
您还可以从主进程生成服务员实例。在这种情况下,将工人的 PID 传递给服务员。然后服务员可以调用 FreeConsole
and AttachConsole
将自己附加到工作人员的控制台。这样比较复杂,但是可以方便主进程终止waiter。
通过更改 stdout 和 stderr,您实际上可以获得所需的信息,例如使用 pdb
(python's built in debugging library)。我喜欢这种方法,而不是保持 window 打开,因为它可以移植到无头环境。
根据 Popen docs 将 stdin
设置为 subprocess.PIPE
并将 stderr
设置为 subprocess.STDOUT
如果您希望在同一缓冲区中捕获常规输出和错误输出.我个人更喜欢这个用于调试。否则将两者都设置为 PIPE
.
不要忘记跟踪您的 Popen
对象,否则您无法检查它们。你的例子看起来像这样:
import subprocess
import time
import pdb
processes = []
for i in range(1, 5):
process = subprocess.Popen(["python", "main.py", str(i), str(i)],
close_fds=False,
creationflags=subprocess.CREATE_NEW_CONSOLE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
processes.append(process)
time.sleep(3)
pdb.set_trace()
请注意,创建的控制台不会显示输出,因为它已被重新路由。
现在,当您进入调试器时,进程应该是 subprocess.Popen
个对象的列表,例如(您的列表当然会更长):
(Pdb) processes
[<subprocess.Popen object at 0x000002D359BD6AF0>, <subprocess.Popen object at 0x000002D359BD6F10>]
轮询进程以找出哪些进程已退出:
(Pdb) processes[0].poll()
1
(Pdb) processes[1].poll()
(Pdb)
这个输出告诉我们列表中的第一个进程已经退出,第二个进程仍然是 运行。
如果你想要一个漂亮的打印输出捕获,请执行以下操作:
(Pdb) print(processes[0].stdout.read().decode())
Traceback (most recent call last):
File "C:\Projects\dicom-nifti-message-service\tests\dicom_nifti_app.py", line 12, in <module>
from conftest import set_progress
File "C:\Projects\dicom-nifti-message-service\tests\conftest.py", line 46, in <module>
from tests.dicom_nifti_app_config import config as config_
ModuleNotFoundError: No module named 'tests.dicom_nifti_app_config'
这个命令看起来有点绕,但是 Popen.stdout
属性是一个缓冲区,你可以在它上面调用 read()
来得到它作为字节串。现在你调用 decode()
来得到一个字符串。使用 print()
在输出中看不到像 \n
这样的字符,但实际上将它们解释为换行符等
小提示:请注意,要在调试时访问 运行 进程的输出,您必须先将其杀死!按如下方式进行,或查看文档以了解其他方法。
(Pdb) processes[1].poll()
(Pdb) processes[1].kill()
(Pdb) processes[1].poll()
1
(Pdb)
如果您对实时调试不感兴趣,您可以:
- 使用此方法将捕获的输出写入文件
- 或者甚至更简单,从您在子进程运行中的程序
登录到文件
我的目标是为每个 Popen
进程使用几个不同的参数调用主程序,每个进程都有自己的控制台 window。然而,一旦遇到崩溃,它就会关闭那个控制台,我真的很想让它保持打开状态。
import subprocess
from subprocess import CREATE_NEW_CONSOLE
import time
for i in range(1, 5):
subprocess.Popen(["python", "main.py", str(i), str(i)], close_fds=False, creationflags=CREATE_NEW_CONSOLE)
time.sleep(3)
是否可以使用 subprocess.Popen
创建一个新进程并保持控制台打开以便我读取错误?
Link 到 subprocess docs.
控制台 window 由 conhost.exe (Win 7+) 的实例托管,当没有进程附加到它时退出。所以你只需要将第二个 python.exe 进程附加到每个控制台并让它等待。这是一个简单的演示脚本,它使等待进程等待主进程(即工作进程的父进程):
import sys
# If started with a pid, wait for the associated process to exit.
if len(sys.argv) > 1:
import ctypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
SYNCHRONIZE = 0x00100000
pid = int(sys.argv[1])
hproc = kernel32.OpenProcess(SYNCHRONIZE, 0, pid)
if not hproc:
sys.exit(ctypes.get_last_error())
print('waiter: waiting')
kernel32.WaitForSingleObject(hproc, -1)
sys.exit(0)
import os
import subprocess
print('worker: start')
# The waiter waits on our parent (ppid).
p = subprocess.Popen([sys.executable, sys.argv[0], str(os.getppid())])
print('worker: exit')
例如:
>>> import sys, subprocess
>>> flags = subprocess.CREATE_NEW_CONSOLE
>>> p = subprocess.Popen([sys.executable, 'main.py'], creationflags=flags)
>>> p.wait()
0
>>> exit()
工作进程重新生成自身以等待其父进程。然后它以 return 代码 0 退出。控制台 window 在 exit()
后关闭。
您还可以从主进程生成服务员实例。在这种情况下,将工人的 PID 传递给服务员。然后服务员可以调用 FreeConsole
and AttachConsole
将自己附加到工作人员的控制台。这样比较复杂,但是可以方便主进程终止waiter。
通过更改 stdout 和 stderr,您实际上可以获得所需的信息,例如使用 pdb
(python's built in debugging library)。我喜欢这种方法,而不是保持 window 打开,因为它可以移植到无头环境。
根据 Popen docs 将 stdin
设置为 subprocess.PIPE
并将 stderr
设置为 subprocess.STDOUT
如果您希望在同一缓冲区中捕获常规输出和错误输出.我个人更喜欢这个用于调试。否则将两者都设置为 PIPE
.
不要忘记跟踪您的 Popen
对象,否则您无法检查它们。你的例子看起来像这样:
import subprocess
import time
import pdb
processes = []
for i in range(1, 5):
process = subprocess.Popen(["python", "main.py", str(i), str(i)],
close_fds=False,
creationflags=subprocess.CREATE_NEW_CONSOLE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
processes.append(process)
time.sleep(3)
pdb.set_trace()
请注意,创建的控制台不会显示输出,因为它已被重新路由。
现在,当您进入调试器时,进程应该是 subprocess.Popen
个对象的列表,例如(您的列表当然会更长):
(Pdb) processes
[<subprocess.Popen object at 0x000002D359BD6AF0>, <subprocess.Popen object at 0x000002D359BD6F10>]
轮询进程以找出哪些进程已退出:
(Pdb) processes[0].poll()
1
(Pdb) processes[1].poll()
(Pdb)
这个输出告诉我们列表中的第一个进程已经退出,第二个进程仍然是 运行。 如果你想要一个漂亮的打印输出捕获,请执行以下操作:
(Pdb) print(processes[0].stdout.read().decode())
Traceback (most recent call last):
File "C:\Projects\dicom-nifti-message-service\tests\dicom_nifti_app.py", line 12, in <module>
from conftest import set_progress
File "C:\Projects\dicom-nifti-message-service\tests\conftest.py", line 46, in <module>
from tests.dicom_nifti_app_config import config as config_
ModuleNotFoundError: No module named 'tests.dicom_nifti_app_config'
这个命令看起来有点绕,但是 Popen.stdout
属性是一个缓冲区,你可以在它上面调用 read()
来得到它作为字节串。现在你调用 decode()
来得到一个字符串。使用 print()
在输出中看不到像 \n
这样的字符,但实际上将它们解释为换行符等
小提示:请注意,要在调试时访问 运行 进程的输出,您必须先将其杀死!按如下方式进行,或查看文档以了解其他方法。
(Pdb) processes[1].poll()
(Pdb) processes[1].kill()
(Pdb) processes[1].poll()
1
(Pdb)
如果您对实时调试不感兴趣,您可以:
- 使用此方法将捕获的输出写入文件
- 或者甚至更简单,从您在子进程运行中的程序 登录到文件