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