python progress-bar subprocess event-driven

Llamada al sistema controlada por eventos en Python



progress-bar subprocess (1)

Estoy tratando de implementar un proceso impulsado por eventos con llamada al sistema o subproceso. Básicamente, quiero iniciar un comando del sistema sin bloqueo y, una vez completada esa llamada del sistema, quiero que se llame a una función. Esto es para que pueda iniciar una barra de progreso de la GUI, iniciar un comando del sistema y hacer que la barra de progreso continúe, y cuando finalice la llamada del sistema, haga que la barra de progreso se detenga.

Lo que NO quiero HACER absolutamente es generar un proceso, obtener su ID de proceso y seguir comprobando la finalización de ese proceso en un ciclo while.

A continuación se muestra solo un ejemplo de cómo imagino que esto debería funcionar (Todos estos están dentro de una clase)

def launchTool(self): self.progressbar.config(mode = ''indeterminate'') self.progressbar.start(20) self.launchButton.config(state = ''disabled'') self.configCombobox.config(state = ''disabled'') ## here the "onCompletion" is a pointer to a function call("/usr/bin/make psf2_dcf", shell=True, onCompletion = self.toolCompleted) def onCompletion(self): print(''DONE running Tool'') self.progressbar.stop() self.launchButton.config(state = ''normal'') self.configCombobox.config(state = ''normal'')


Para evitar el estado del subproceso de sondeo, puede usar la señal SIGCHLD en Unix. Para combinarlo con el bucle de eventos de tkinter, puede usar el truco de autotubo. También soluciona el posible problema de señal tkinter + sin la necesidad de activar el bucle de eventos periódicamente.

#!/usr/bin/env python3 import logging import os import signal import subprocess import tkinter info = logging.getLogger(__name__).info def on_signal(pipe, mask, count=[0]): try: signals = os.read(pipe, 512) except BlockingIOError: return # signals have been already dealt with # from asyncio/unix_events.py #+start # Because of signal coalescing, we must keep calling waitpid() as # long as we''re able to reap a child. while True: try: pid, status = os.waitpid(-1, os.WNOHANG) except ChildProcessError: info(''No more child processes exist.'') return else: if pid == 0: info(''A child process is still alive. signals=%r%s'', signals, '' SIGCHLD''*(any(signum == signal.SIGCHLD for signum in signals))) return #+end # you could call your callback here info(''{pid} child exited with status {status}''.format(**vars())) count[0] += 1 if count[0] == 2: root.destroy() # exit GUI logging.basicConfig(format="%(asctime)-15s %(message)s", datefmt=''%F %T'', level=logging.INFO) root = tkinter.Tk() root.withdraw() # hide GUI r, w = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC) signal.set_wakeup_fd(w) root.createfilehandler(r, tkinter.READABLE, on_signal) signal.signal(signal.SIGCHLD, lambda signum, frame: None) # enable SIGCHLD signal.siginterrupt(signal.SIGCHLD, False) # restart interrupted syscalls automatically info(''run children'') p = subprocess.Popen(''sleep 4'', shell=True) subprocess.Popen(''sleep 1'', shell=True) root.after(2000, p.send_signal, signal.SIGSTOP) # show that SIGCHLD may be delivered root.after(3000, p.send_signal, signal.SIGCONT) # while the child is still alive root.after(5000, lambda: p.poll() is None and p.kill()) # kill it root.mainloop() info(''done'')

Salida

2015-05-20 23:39:50 run children 2015-05-20 23:39:51 16991 child exited with status 0 2015-05-20 23:39:51 A child process is still alive. signals=b''/x11'' SIGCHLD 2015-05-20 23:39:52 A child process is still alive. signals=b''/x11'' SIGCHLD 2015-05-20 23:39:53 A child process is still alive. signals=b''/x11'' SIGCHLD 2015-05-20 23:39:54 16989 child exited with status 0 2015-05-20 23:39:54 No more child processes exist. 2015-05-20 23:39:54 done