Popen 在子进程已终止时仍等待子进程的问题

21

我正在Windows 8/XP上使用Python 2.7。

我有一个程序A,它使用以下代码运行另一个程序B:

p = Popen(["B"], stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
return

B运行一个批处理脚本C。C是一个长时间运行的脚本,我希望即使C没有完成,B也能退出。我已经使用以下代码(在B中)实现了这一点:

B运行批处理脚本C,即使C没有完成,也希望B能够退出。我使用以下代码(在B中)实现了这个目的:

p = Popen(["C"])
return

当我运行B时,它按预期工作。但是当我运行A时,我希望它在B退出后也能退出。但是A会等到C退出后才退出,尽管B已经退出了。有什么想法和可能的解决方案吗?

不幸的是,将A更改为类似B的明显解决方案不可行。

以下是一个功能示例代码,以说明这个问题: https://www.dropbox.com/s/cbplwjpmydogvu2/popen.zip?dl=1

该zip文件包括以下文件及其内容:

A.py

from subprocess import PIPE, Popen
import sys

def log(line):
    with open("log.txt", "a") as logfile:
        logfile.write(line)

log("\r\n\r\nA: I'll wait for B\r\n")

p = Popen(["C:\\Python27\\python.exe", "B.py"], stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()

log("A: Done.\r\n")
sys.exit(0)

B.py

from subprocess import Popen, PIPE
import sys

def log(line):
    with open("log.txt", "a") as logfile:
        logfile.write(line)

log("B: launching C\r\n")

p = Popen(["C.bat"])

log("B: Not waiting for C at all. bye!\r\n")
sys.exit(0)

C.bat

@echo off
echo C: Start long running task : %time% >>  "log.txt"
ping -n 10 127.0.0.1>nul
echo C: Stop long running task : %time% >>  "log.txt"

非常感谢您的反馈。


如果我理解正确的话,您有一个运行程序B的程序A,而程序B又运行程序C。程序A还运行程序C。这是正确的吗? - Some programmer dude
不,程序A并不直接运行程序C。如果您查看附带的示例,那就太好了。谢谢。 - khattam
那么第二个Popen是来自程序B吗? - Some programmer dude
是的。A只运行B,与C没有直接关系。 - khattam
给第二个 Popen() 调用提供 close_fds=True 参数有帮助吗?我猜 C 会继承 A 的标准输出/错误管道,因此 A 会等待直到 C 关闭它们。 - Simon
我尝试在第二个程序(B)中使用 close_fds=True,但似乎没有任何区别。你能否请检查附上的示例,并看看是否有什么可以做的? - khattam
3个回答

28

您可以为C子进程提供start_new_session类似的函数:

#!/usr/bin/env python
import os
import sys
import platform
from subprocess import Popen, PIPE

# set system/version dependent "start_new_session" analogs
kwargs = {}
if platform.system() == 'Windows':
    # from msdn [1]
    CREATE_NEW_PROCESS_GROUP = 0x00000200  # note: could get it from subprocess
    DETACHED_PROCESS = 0x00000008          # 0x8 | 0x200 == 0x208
    kwargs.update(creationflags=DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP)  
elif sys.version_info < (3, 2):  # assume posix
    kwargs.update(preexec_fn=os.setsid)
else:  # Python 3.2+ and Unix
    kwargs.update(start_new_session=True)

p = Popen(["C"], stdin=PIPE, stdout=PIPE, stderr=PIPE, **kwargs)
assert not p.poll()

[1]: CreateProcess()的进程创建标志


1
如果我在Windows上将close_fds=True添加到Windows kwargs行中,则此方法仅对我有效:kwargs.update(creationflags=DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP, close_fds=True)(请参见此答案)。 - jtpereyda
1
@jtpereyda 你有没有注意到,如果你重定向了stdin、stdout或stderr中的任何一个,就不能在Windows上使用close_fds=True - jfs
@J.F.Sebastian 是的,我理解文档的意思是,如果你使用close_fds=True,stdin、stdout和stderr将不起作用(尽管措辞有点模糊,不太清楚如果你实际使用它们会发生什么)。在我的代码片段中,只需添加close_fds就可以很好地工作,但在Windows平台上,最好省略stdin/stdout/stderr。 - jtpereyda
@jtpereyda:代码的意图是 std*=DEVNULL,也就是说,在这里不能省略 stdin/stdout/stderr(至少在 POSIX 上不行——close_fds=True 在那里不会关闭 std*)。除非你打开了其他(非标准)文件描述符(要么关闭它们,要么传递 close_fds=True——在 Python 3 的 POSIX 上默认为此),否则不需要 close_fds=True。在 Windows 上,close_fds=True 就足够了(不需要重定向),这样子进程就不会继承父进程的文件描述符(我没有测试在这种情况下如果你写入 stdout 或者同时在 Windows 上传递 std*=DEVNULLclose_fds=True 会发生什么)。 - jfs
2
请注意,从Python 3.7开始,在Windows上不再需要设置close_fds=True - 这是默认值,并且可以与重定向stdin/stdout/stderr一起使用。有关详细信息,请参见错误跟踪器:https://bugs.python.org/issue19764 - nirvana-msu
显示剩余3条评论

0

另一个选择是将C作为完全分离的进程在单独的进程树中启动,例如通过cmd.exe的start命令:

import subprocess
subprocess.Popen(["cmd.exe", "/C", "start notepad"])

显然,由于它是完全独立的,您无法与其通信。但是,您可以使用psutil检索其PID,以便至少可以监视或关闭它(如果必要):

import psutil
for process in psutil.process_iter():
    if process.name() == 'notepad.exe':
        print(process.pid)

-1

这里是一个代码片段,改编自Sebastian的回答和this的回答:

#!/usr/bin/env python
import os
import sys
import platform
from subprocess import Popen, PIPE

# set system/version dependent "start_new_session" analogs
kwargs = {}
if platform.system() == 'Windows':
    # from msdn [1]
    CREATE_NEW_PROCESS_GROUP = 0x00000200  # note: could get it from subprocess
    DETACHED_PROCESS = 0x00000008          # 0x8 | 0x200 == 0x208
    kwargs.update(creationflags=DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP, close_fds=True)  
elif sys.version_info < (3, 2):  # assume posix
    kwargs.update(preexec_fn=os.setsid)
else:  # Python 3.2+ and Unix
    kwargs.update(start_new_session=True)

p = Popen(["C"], stdin=PIPE, stdout=PIPE, stderr=PIPE, **kwargs)
assert not p.poll()

我个人只在Windows上进行了测试。


2
请注意,从Python 3.7开始,在Windows上不再需要设置“close_fds=True” - 现在默认情况下可以与重定向的stdin/stdout/stderr一起使用。有关详细信息,请参见错误跟踪器:https://bugs.python.org/issue19764 - nirvana-msu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接