当我在OSX或Linux上运行以下代码然后按ctrl+c
时,将启动“正常关闭”。看起来像这样:
$ python subprocess_test.py
Subprocess: <MyProcess(MyProcess-1, started)>
^CMain: Graceful shutdown
Subprocess: shutdown
但是,当我在Windows10计算机上运行某些代码时,行KeyboardInterrupt
中会引发self.event.wait()
,导致正常关闭。我已经尝试了here描述的不同方法来防止子进程正在接收信号。
使用Python 2.7在不同操作系统上获得相同行为的正确方法是什么?
import multiprocessing
import signal
class MyProcess(multiprocessing.Process):
def __init__(self):
super(MyProcess, self).__init__()
self.event = multiprocessing.Event()
def run(self):
print "Subprocess: ", multiprocessing.current_process()
self.event.wait()
print "Subprocess: shutdown"
def sighandler(a,b,):
print "Main: Graceful shutdown"
p1.event.set()
def run():
signal.signal(signal.SIGINT, signal.SIG_IGN)
global p1
p1 = MyProcess()
p1.start()
signal.signal(signal.SIGINT, sighandler)
p1.join()
if __name__ == '__main__':
run()
答案 0 :(得分:1)
在Windows SIGINT
上使用CTRL_C_EVENT
的控制台控件事件处理程序实现。它是由子进程继承的控制台状态,而不是CRT的信号处理状态。因此,如果您希望孩子忽略Ctrl + C,则需要先在父进程中调用SetConsoleCtrlHandler
忽略Ctrl + C,然后再创建子进程。
有一个问题。 Python不在Windows上使用可警告的等待,例如在进程join
方法中等待。由于它在主线程上调度信号处理程序,因此主线程在join()
中被阻塞的事实意味着永远不会调用您的信号处理程序。您必须使用time.sleep()
上的循环替换连接,这可以通过Ctrl + C进行中断,因为它在内部等待Windows事件并设置自己的控制处理程序来设置此事件。或者您可以通过ctypes使用自己的异步控制处理程序集。以下示例实现了这两种方法,并在Python 2和3中都有效。
import sys
import signal
import multiprocessing
if sys.platform == "win32":
# Handle Ctrl+C in the Windows Console
import time
import errno
import ctypes
import threading
from ctypes import wintypes
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
PHANDLER_ROUTINE = ctypes.WINFUNCTYPE(
wintypes.BOOL,
wintypes.DWORD) # _In_ dwCtrlType
win_ignore_ctrl_c = PHANDLER_ROUTINE() # alias for NULL handler
def _errcheck_bool(result, func, args):
if not result:
raise ctypes.WinError(ctypes.get_last_error())
return args
kernel32.SetConsoleCtrlHandler.errcheck = _errcheck_bool
kernel32.SetConsoleCtrlHandler.argtypes = (
PHANDLER_ROUTINE, # _In_opt_ HandlerRoutine
wintypes.BOOL) # _In_ Add
class MyProcess(multiprocessing.Process):
def __init__(self):
super(MyProcess, self).__init__()
self.event = multiprocessing.Event()
def run(self):
print("Subprocess: %r" % multiprocessing.current_process())
self.event.wait()
print("Subprocess: shutdown")
if sys.platform == "win32":
def join(self, timeout=None):
if threading.current_thread().name != "MainThread":
super(MyProcess, self).join(timeout)
else:
# use time.sleep to allow the main thread to
# interruptible by Ctrl+C
interval = 1
remaining = timeout
while self.is_alive():
if timeout is not None:
if remaining <= 0:
break
if remaining < interval:
interval = remaining
remaining = 0
else:
remaining -= interval
try:
time.sleep(interval)
except IOError as e:
if e.errno != errno.EINTR:
raise
break
def run():
p1 = MyProcess()
# Ignore Ctrl+C, which is inherited by the child process.
if sys.platform == "win32":
kernel32.SetConsoleCtrlHandler(win_ignore_ctrl_c, True)
signal.signal(signal.SIGINT, signal.SIG_IGN)
p1.start()
# Set a Ctrl+C handler to signal graceful shutdown.
if sys.platform == "win32":
kernel32.SetConsoleCtrlHandler(win_ignore_ctrl_c, False)
# comment out the following to rely on sig_handler
# instead. Note that using the normal sig_handler requires
# joining using a loop on time.sleep() instead of the
# normal process join method. See the join() method
# defined above.
@PHANDLER_ROUTINE
def win_ctrl_handler(dwCtrlType):
if (dwCtrlType == signal.CTRL_C_EVENT and
not p1.event.is_set()):
print("Main <win_ctrl_handler>: Graceful shutdown")
p1.event.set()
return False
kernel32.SetConsoleCtrlHandler(win_ctrl_handler, True)
def sig_handler(signum, frame):
if not p1.event.is_set():
print("Main <sig_handler>: Graceful shutdown")
p1.event.set()
signal.signal(signal.SIGINT, sig_handler)
p1.join()
if __name__ == "__main__":
run()
答案 1 :(得分:1)
使用pywin32中的win32api.SetConsoleCtrlHandler
可以控制Windows的中断方式。使用SetConsoleCtrlHandler(None, True)
会导致调用进程忽略CTRL + C输入。使用SetConsoleCtrlHandler(sighandler, True)
可以注册特定的处理程序。
将所有问题全部解决,问题如下:
import multiprocessing
import signal
import sys
class MyProcess(multiprocessing.Process):
def __init__(self):
super(MyProcess, self).__init__()
self.event = multiprocessing.Event()
def run(self):
if sys.platform == "win32":
import win32api # ignoring the signal
win32api.SetConsoleCtrlHandler(None, True)
print "Subprocess: ", multiprocessing.current_process()
self.event.wait()
print "Subprocess: shutdown"
def sighandler(a,b=None):
print "Main: Graceful shutdown"
p1.event.set()
def run():
signal.signal(signal.SIGINT, signal.SIG_IGN)
global p1
p1 = MyProcess()
p1.start()
if sys.platform == "win32":
import win32api
win32api.SetConsoleCtrlHandler(sighandler, True)
else:
signal.signal(signal.SIGINT, sighandler)
p1.join()
if __name__ == '__main__':
run()