繁体   English   中英

等待第一个子进程完成

[英]Wait for the first subprocess to finish

我有一个subprocess进程列表。 我不与他们交流,只是等待。

我想等待第一个过程完成(此解决方案有效):

import subprocess

a = subprocess.Popen(['...'])
b = subprocess.Popen(['...'])

# wait for the first process to finish
while True:
    over = False
    for child in {a, b}:
        try:
            rst = child.wait(timeout=5)
        except subprocess.TimeoutExpired:
            continue  # this subprocess is still running

        if rst is not None:  # subprocess is no more running
            over = True
            break  # If either subprocess exits, so do we.
    if over:
        break

我不想使用os.wait() ,因为它可能从另一个subprocess进程返回,而不是我正在等待的列表的一部分。

一个漂亮而优雅的解决方案可能是使用epoll或 select 并且没有任何循环。

这是使用 psutil 的解决方案 -正是针对此用例:

import subprocess
import psutil

a = subprocess.Popen(['/bin/sleep', "2"])

b = subprocess.Popen(['/bin/sleep', "4"])

procs_list = [psutil.Process(a.pid), psutil.Process(b.pid)]

def on_terminate(proc):
     print("process {} terminated".format(proc))

# waits for multiple processes to terminate
gone, alive = psutil.wait_procs(procs_list, timeout=3, callback=on_terminate)

或者,如果您希望有一个循环等待某个过程完成:

while True: 
    gone, alive = psutil.wait_procs(procs_list, timeout=3, callback=on_terminate) 
    if len(gone)>0: 
        break

如果您不需要从进程中获取 output,则Popen.poll()似乎是检查它们是否完成的最简单方法。 下面的while True循环纯粹是为了演示目的:您可以决定如何在较大的程序中执行此操作(例如,在单独的线程中进行检查,在程序的其他工作之间进行检查等)。

from subprocess import Popen
from time import sleep

ps = [
    Popen(['sleep', t])
    for t in ('3', '5', '2')
]

while True:
    exit_codes = [p.poll() for p in ps]
    print(exit_codes)
    if any(ec is not None for ec in exit_codes):
        break
    else:
        sleep(1)

演示 output:

[None, None, None]
[None, None, None]
[None, None, 0]

可以为此使用os.wait() 您只需循环调用它,直到它告诉您您关心的进程之一已经退出。

import subprocess

a = subprocess.Popen(['...'])

b = subprocess.Popen(['...'])


# wait for the first process to finish
watched_pids = set(proc.pid for proc in (a, b))
while True:
    pid, _ = os.wait()
    if pid in watched_pids:
        break

然而, os.wait()隐藏的副作用是您丢失了进程的退出代码。 os.wait()完成后它将只是None ,如果您稍后调用proc.wait()proc.poll()proc.communicate()他们将无法找到返回码并默认为 0。它可以自己设置,但这有点hacky。

def wait_and_handle_exitstatus(all_procs):
    pid, status = os.wait()
    for proc in all_procs:
        if proc.pid == pid:
            # We need to set the process's exit status now, or we
            # won't be able to retrieve it later and it will be
            # assumed to be 0.
            # This is a kind of hacky solution, but this function has existed
            # ever since subprocess was first included in the stdlib and is
            # still there in 3.10+, so it *should* be pretty stable.
            proc._handle_exitstatus(status)
    return pid, status

然后您可以使用第一个代码块,只需将os.wait()替换为wait_and_handle_exitstatus(ALL_PROCS) 但是,您必须向 wait_and_handle_exitstatus 传递可能正在运行的所有子Popenwait_and_handle_exitstatus对象)的列表,并且您可能会关心它的返回代码,以便它可以找到该进程并设置其退出代码。

使用asyncio.waitasyncio.as_completed

import asyncio

async def example():
    p1 = await asyncio.create_subprocess_exec("sleep", "1")
    p2 = await asyncio.create_subprocess_exec("sleep", "2")
    p1_run = asyncio.create_task(p1.wait())
    p2_run = asyncio.create_task(p2.wait())
    pending = [p1_run, p2_run]
    while pending:
        done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
        if p1_run in done:
            print("p1 finished, with status: ", p1.returncode)
        if p2_run in done:
            print("p2 finished, with status: ", p2.returncode)

asyncio.get_event_loop().run_until_complete(example())

为了避免重复执行 p1 和 p2 中的哪一个,您通常会得到一个更复杂的 px_run 到 px 的映射。

为了避免这种情况,另一种选择是将任务包装在下面的wait_and_return_original中,下一个示例还使用更方便的asyncio.as_completed

async def wait_and_return_original(proc: asyncio.subprocess):
    await proc.wait()
    return proc

async def example2():
    p1 = await asyncio.create_subprocess_exec("sleep", "1")
    p2 = await asyncio.create_subprocess_exec("sleep", "2")
    
    for p in asyncio.as_completed([wait_and_return_original(p) for p in [p1, p2]]):
        p_completed = await p   # NOTE: for-loop iteration variable doesn't decide which task is first completed until here!
        if p_completed is p1:
            print("p1 finished, with status: ", p1.returncode)
        if p_completed is p2:
            print("p2 finished, with status: ", p2.returncode)

asyncio.get_event_loop().run_until_complete(example2())

有两种方法可以做到这一点,如果您希望命令阻塞并暂停程序直到它完成,请使用subprocess.call

a = subprocess.call('...')
b = subprocess.call('...')

我不认为这是你想要的。

如果您不希望他们停止整个程序并且只需要在调用另一个之前检查其中一个是否完成,您应该使用.communicate

```py
a = subprocess.Popen(['...'])

b = subprocess.Popen(['...'])

....
    for child in {a, b}:
        try:
            result, err = child.communicate(timeout=5)

.communicate几乎是最优雅、最简单和最推荐的解决方案

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM