簡體   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