Python en Windows: ¿cómo esperar varios procesos secundarios?

¿Cómo esperar varios procesos secundarios en Python en Windows, sin espera activa (sondeo)? Algo como esto casi me funciona:

proc1 = subprocess.Popen(['python','mytest.py']) proc2 = subprocess.Popen(['python','mytest.py']) proc1.wait() print "1 finished" proc2.wait() print "2 finished" 

El problema es que cuando proc2 finaliza antes de proc1 , el proceso principal todavía esperará a proc1 . En Unix, se usaría waitpid(0) en un bucle para obtener los códigos de retorno de los procesos secundarios a medida que finalizan. ¿Cómo lograr algo como esto en Python en Windows?

Puede parecer una exageración, pero aquí va:

 import Queue, thread, subprocess results= Queue.Queue() def process_waiter(popen, description, que): try: popen.wait() finally: que.put( (description, popen.returncode) ) process_count= 0 proc1= subprocess.Popen( ['python', 'mytest.py'] ) thread.start_new_thread(process_waiter, (proc1, "1 finished", results)) process_count+= 1 proc2= subprocess.Popen( ['python', 'mytest.py'] ) thread.start_new_thread(process_waiter, (proc2, "2 finished", results)) process_count+= 1 # etc while process_count > 0: description, rc= results.get() print "job", description, "ended with rc =", rc process_count-= 1 

Twisted tiene una API de generación de procesos asíncrona que funciona en Windows. En realidad, hay varias implementaciones diferentes, muchas de las cuales no son tan buenas, pero puedes cambiar entre ellas sin cambiar tu código.

Sobre la base de la respuesta de zseil, puede hacer esto con una combinación de subprocesos y llamadas de API win32. Utilicé ctypes directos, porque mi Python no tiene instalado win32api. Estoy generando sleep.exe desde MSYS aquí como ejemplo, pero claramente puedes generar cualquier proceso que desees. Uso OpenProcess () para obtener un HANDLE del PID del proceso, y luego WaitForMultipleObjects para esperar a que finalice cualquier proceso.

 import ctypes, subprocess from random import randint SYNCHRONIZE=0x00100000 INFINITE = -1 numprocs = 5 handles = {} for i in xrange(numprocs): sleeptime = randint(5,10) p = subprocess.Popen([r"c:\msys\1.0\bin\sleep.exe", str(sleeptime)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False) h = ctypes.windll.kernel32.OpenProcess(SYNCHRONIZE, False, p.pid) handles[h] = p.pid print "Spawned Process %d" % p.pid while len(handles) > 0: print "Waiting for %d children..." % len(handles) arrtype = ctypes.c_long * len(handles) handle_array = arrtype(*handles.keys()) ret = ctypes.windll.kernel32.WaitForMultipleObjects(len(handle_array), handle_array, False, INFINITE) h = handle_array[ret] ctypes.windll.kernel32.CloseHandle(h) print "Process %d done" % handles[h] del handles[h] print "All done!" 

Twisted en Windows realizará una espera activa debajo de las portadas. Si no desea utilizar subprocesos, tendrá que usar la API de win32 para evitar el sondeo. Algo como esto:

 import win32process import win32event # Note: CreateProcess() args are somewhat cryptic, look them up on MSDN proc1, thread1, pid1, tid1 = win32process.CreateProcess(...) proc2, thread2, pid2, tid2 = win32process.CreateProcess(...) thread1.close() thread2.close() processes = {proc1: "proc1", proc2: "proc2"} while processes: handles = processes.keys() # Note: WaitForMultipleObjects() supports at most 64 processes at a time index = win32event.WaitForMultipleObjects(handles, False, win32event.INFINITE) finished = handles[index] exitcode = win32process.GetExitCodeProcess(finished) procname = processes.pop(finished) finished.close() print "Subprocess %s finished with exit code %d" % (procname, exitcode) 

Puedes usar psutil :

 >>> import subprocess >>> import psutil >>> >>> proc1 = subprocess.Popen(['python','mytest.py']) >>> proc2 = subprocess.Popen(['python','mytest.py']) >>> ls = [psutil.Process(proc1.pid), psutil.Process(proc2.pid)] >>> >>> gone, alive = psutil.wait_procs(ls, timeout=3) 

‘ido’ y ‘vivo’ son listas que indican qué procesos se han ido y cuáles aún están vivos.

Opcionalmente, puede especificar una callback que se invoque cada vez que finalice uno de los procesos observados:

 >>> def on_terminate(proc): ... print "%s terminated" % proc ... >>> gone, alive = psutil.wait_procs(ls, timeout=3, callback=on_terminate)