简体   繁体   English

等待第一个子进程完成

[英]Wait for the first subprocess to finish

I have a list of subprocess ' processes.我有一个subprocess进程列表。 I do not communicate with them and just wait.我不与他们交流,只是等待。

I want to wait for the first process to finish (this solution works):我想等待第一个过程完成(此解决方案有效):

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

I don't want use os.wait() , cause it could return from another subprocess not part of the list I'm waiting for.我不想使用os.wait() ,因为它可能从另一个subprocess进程返回,而不是我正在等待的列表的一部分。

A nice and elegant solution would probably be with an epoll or select and without any loop.一个漂亮而优雅的解决方案可能是使用epoll或 select 并且没有任何循环。

Here's a solution using psutil - which is aimed exactly at this use-case:这是使用 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)

Or, if you'd like to have a loop waiting for one of the process to be done:或者,如果您希望有一个循环等待某个过程完成:

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

If you don't need to get output from the processes, Popen.poll() seems to be the simplest way to check whether they are done.如果您不需要从进程中获取 output,则Popen.poll()似乎是检查它们是否完成的最简单方法。 The while True loop below is purely for demonstration purposes: you can decide how to do this in your larger program (eg, do the checking in a separate thread, do the checking in between the other work of the program, etc).下面的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)

Demo output:演示 output:

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

You can use os.wait() for this.可以为此使用os.wait() You just have to call it in a loop until it tells you one of the processes you care about has exited.您只需循环调用它,直到它告诉您您关心的进程之一已经退出。

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

However, a hidden side effect of os.wait() is that you lose the process's exit code.然而, os.wait()隐藏的副作用是您丢失了进程的退出代码。 It will just be None after os.wait() finishes, and if you later call proc.wait() , proc.poll() , or proc.communicate() they will fail to find the return code and default to 0. It is possible to set it yourself, but it's kind of hacky.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

You can then use the first code block, only replace os.wait() with wait_and_handle_exitstatus(ALL_PROCS) .然后您可以使用第一个代码块,只需将os.wait()替换为wait_and_handle_exitstatus(ALL_PROCS) You do, however, have to pass wait_and_handle_exitstatus a list of all subprocesses ( Popen objects) that might be running and you might care about the return code of, so it can find the process and set its exit code.但是,您必须向 wait_and_handle_exitstatus 传递可能正在运行的所有子Popenwait_and_handle_exitstatus对象)的列表,并且您可能会关心它的返回代码,以便它可以找到该进程并设置其退出代码。

Using asyncio.wait or asyncio.as_completed :使用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())

To avoid having to repeat which one of p1 and p2 that was done, you usually end up with a more complex mapping of the px_run to px.为了避免重复执行 p1 和 p2 中的哪一个,您通常会得到一个更复杂的 px_run 到 px 的映射。

To avoid that, another option is to wrap the task in something like wait_and_return_original below, next example also uses a more convenient asyncio.as_completed为了避免这种情况,另一种选择是将任务包装在下面的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())

There are pretty much 2 ways to do this, if you want the commands to be blocking and halt your program until it finishes, use subprocess.call有两种方法可以做到这一点,如果您希望命令阻塞并暂停程序直到它完成,请使用subprocess.call

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

I don't think this is what you want though.我不认为这是你想要的。

If you do not want them to halt your entire program and only need to check if one of them is finished before calling another, you should use .communicate如果您不希望他们停止整个程序并且只需要在调用另一个之前检查其中一个是否完成,您应该使用.communicate

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

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

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

.communicate is pretty much the most elegant, simple and recommended solution .communicate几乎是最优雅、最简单和最推荐的解决方案

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

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