python 调用event handler_Python中的事件驱动系统调用

I'm trying to implement an event driven process with system call or subprocess. Basically I want to launch a non-blocking system command and upon completion of that system call, I want a function to be called.

This is so that I can start a GUI progress bar, launch a system command and have the progress bar continue, and when the system call finishes, have the progress bar stop.

What I want to absolutely NOT DO, is to spawn a process, get its process ID and keep checking for the completion of that process in a while loop.

Below is just an example of how I imagine this should work (All of these are inside a class)

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')

解决方案

To avoid polling subprocess' status, you could use SIGCHLD signal on Unix. To combine it with tkinter's event loop, you could use the self-pipe trick. It also workarounds the possible tkinter + signal issue without the need to wake the event loop periodically.

#!/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')

Output

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