如何在windows机器上处理python中的信号 [英] How to handle the signal in python on windows machine

查看:59
本文介绍了如何在windows机器上处理python中的信号的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在 Windows 上粘贴下面的代码,但它没有处理信号,而是杀死了进程.但是,相同的代码在 Ubuntu 中也能运行.

import os, sys导入时间输入信号def func(signum, frame):print '你提出了一个 SigInt!使用信号',符号调用的信号处理程序信号.信号(信号.SIGINT,功能)为真:打印正在运行...",os.getpid()时间.sleep(2)os.kill(os.getpid(),signal.SIGINT)

解决方案

Python 的 os.kill 在 Windows 上包装了两个不相关的 API.当 sig 参数为 sig 时,它会调用 GenerateConsoleCtrlEventCTRL_C_EVENTCTRL_BREAK_EVENT.在这种情况下,pid 参数是进程组 ID.如果后一个调用失败,并且对于所有其他 sig 值,它会调用 OpenProcess 然后是 TerminateProcess.在这种情况下,pid 参数是进程 ID,sig 值作为退出代码传递.终止 Windows 进程类似于将 SIGKILL 发送到 POSIX 进程.通常应该避免这种情况,因为它不允许进程干净地退出.

请注意,os.kill 的文档错误地声称kill() 还需要杀死进程句柄",这从来都不是真的.它调用 OpenProcess 来获取进程句柄.

决定使用 WinAPI CTRL_C_EVENTCTRL_BREAK_EVENT,而不是 SIGINTSIGBREAK,对于交叉来说是不幸的-平台代码.当传递一个不是进程组 ID 的进程 ID 时,它也没有定义 GenerateConsoleCtrlEvent 做什么.在采用进程 ID 的 API 中使用此函数充其量是可疑的,并且可能是非常错误的.

对于您的特定需求,您可以编写一个适配器函数,使 os.kill 对跨平台代码更加友好.例如:

导入操作系统导入系统导入时间输入信号如果 sys.platform != 'win32':杀 = os.kill睡眠 = 时间.睡眠别的:# 在 Windows 上调整合并的 API.进口螺纹sigmap = {signal.SIGINT:signal.CTRL_C_EVENT,信号.SIGBREAK:信号.CTRL_BREAK_EVENT}def kill(pid, signum):如果 sigmap 和 pid == os.getpid() 中的符号:# 我们不知道当前进程是否是一个# 处理组长,所以只是广播# 连接到此控制台的所有进程.pid = 0线程 = threading.current_thread()handler = signal.getsignal(signum)# 解决调用时的同步问题# 从主线程杀死.如果(sigmap 中的符号和thread.name == 'MainThread' 和可调用(处理程序)和pid == 0):事件 = threading.Event()def handler_set_event(signum, frame):事件.set()返回处理程序(签名,框架)信号.信号(信号,handler_set_event)尝试:os.kill(pid,sigmap [signum])# 忙等待,因为我们不能阻塞在主# 线程,否则信号处理程序无法执行.而不是 event.is_set():经过最后:信号.信号(信号,处理程序)别的:os.kill(pid, sigmap.get(signum, signum))如果 sys.version_info[0] >2:睡眠 = 时间.睡眠别的:导入错误号# 如果信号处理程序没有引发异常,# Python 2 中的 time.sleep 会引发 EINTR IOError,但是# Python 3 只是恢复睡眠.定义睡眠(间隔):'''在 Windows 上忽略 2.x 中的 EINTR 的睡眠'''为真:尝试:t = time.time()时间.睡眠(间隔)除了 IOError 作为 e:如果 e.errno != errno.EINTR:增加间隔 -= time.time() - t如果间隔 <= 0:休息def func(signum, frame):# 注意:不要在信号处理程序中打印.全局 g_signtg_signt = 真#raise 键盘中断信号.信号(信号.SIGINT,功能)g_kill = 错误为真:g_signt = 假g_kill = 不是 g_kill打印('运行 [%d]' % os.getpid())睡觉(2)如果 g_kill:杀死(os.getpid(),信号.SIGINT)如果 g_signt:打印('信号')别的:打印('无信号')

讨论

Windows 没有在系统级别实现信号 [*].Microsoft 的 C 运行时实现了标准 C 所需的六个信号:SIGINTSIGABRTSIGTERMSIGSEGVSIGILLSIGFPE.

SIGABRTSIGTERM 仅针对当前进程实现.您可以通过 C raise 调用处理程序.例如(在 Python 3.5 中):

<预><代码>>>>导入信号,ctypes>>>ucrtbase = ctypes.CDLL('ucrtbase')>>>c_raise = ucrtbase['raise']>>>foo = lambda *a: print('foo')>>>信号.信号(信号.SIGTERM,富)<Handlers.SIG_DFL: 0>>>>c_raise(signal.SIGTERM)富0

SIGTERM 没用.

您也不能使用信号模块对 SIGABRT 做太多事情,因为 abort 函数在处理程序返回后终止进程,这在使用信号模块的内部处理程序时立即发生(它触发注册的 Python 可调用标志以在主线程中调用).对于 Python 3,您可以改用 faulthandler 模块.或者通过ctypes调用CRT的signal函数来设置ctypes回调作为处理程序.

CRT 通过设置 Windows 针对相应的 Windows 异常的结构化异常处理程序:

STATUS_ACCESS_VIOLATION SIGSEGVSTATUS_ILLEGAL_INSTRUCTION 信号STATUS_PRIVILEGED_INSTRUCTION 信号STATUS_FLOAT_DENORMAL_OPERAND SIGFPESTATUS_FLOAT_DIVIDE_BY_ZERO SIGFPESTATUS_FLOAT_INEXACT_RESULT SIGFPESTATUS_FLOAT_INVALID_OPERATION SIGFPESTATUS_FLOAT_OVERFLOW SIGFPESTATUS_FLOAT_STACK_CHECK SIGFPESTATUS_FLOAT_UNDERFLOW SIGFPESTATUS_FLOAT_MULTIPLE_FAULTS SIGFPESTATUS_FLOAT_MULTIPLE_TRAPS SIGFPE

CRT 对这些信号的实现与 Python 的信号处理不兼容.异常过滤器调用注册的处理程序,然后返回 EXCEPTION_CONTINUE_EXECUTION.但是,Python 的处理程序只会触发一个标志,以便解释器稍后在主线程中调用注册的可调用对象.因此,触发异常的错误代码将继续无限循环地触发.在 Python 3 中,您可以将 faulthandler 模块用于这些基于异常的信号.

剩下的 SIGINT,Windows 添加了非标准的 SIGBREAK.控制台和非控制台进程都可以提出这些信号,但只有控制台进程可以从另一个进程接收它们.CRT 通过通过 SetConsoleCtrlHandler 注册控制台控制事件处理程序来实现这一点.

控制台通过在附加进程中创建一个新线程来发送控制事件,该线程在 kernel32.dll 或 kernelbase.dll(未记录)中的 CtrlRoutine 开始执行.处理程序不在主线程上执行会导致同步问题(例如在 REPL 中或使用 input).此外,如果主线程在等待同步对象或等待同步 I/O 完成时被阻塞,则控制事件不会中断主线程.如果主线程可以被 SIGINT 中断,则需要注意避免在主线程中阻塞.Python 3 尝试通过使用 Windows 事件对象来解决此问题,该对象也可用于应该被 SIGINT 中断的等待中.

当控制台向进程发送CTRL_C_EVENTCTRL_BREAK_EVENT 时,CRT 的处理程序调用注册的SIGINTSIGBREAK 处理程序,分别.SIGBREAK 处理程序也会为控制台在其窗口关闭时发送的 CTRL_CLOSE_EVENT 调用.Python 默认通过在主线程中设置 KeyboardInterrupt 来处理 SIGINT.但是,SIGBREAK 最初是默认的 CTRL_BREAK_EVENT 处理程序,它调用 ExitProcess(STATUS_CONTROL_C_EXIT).

您可以通过GenerateConsoleCtrlEvent 向所有附加到当前控制台的进程发送控制事件.这可以针对属于进程组的进程子集,或以组 0 为目标,以将事件发送到连接到当前控制台的所有进程.

进程组不是 Windows API 的一个有据可查的方面.没有用于查询进程组的公共 API,但 Windows 会话中的每个进程都属于一个进程组,即使它只是 wininit.exe 组(服务会话)或 winlogon.exe 组(交互式会话).创建新进程时,通过传递创建标志 CREATE_NEW_PROCESS_GROUP 来创建新组.组 ID 是创建的进程的进程 ID.据我所知,控制台是唯一使用进程组的系统,而这仅适用于 GenerateConsoleCtrlEvent.

当目标 ID 不是进程组 ID 时控制台会做什么是未定义的,不应依赖.如果进程及其父进程都附加到控制台,则向其发送控制事件基本上就像目标是组 0.如果父进程未附加到当前控制台,则 GenerateConsoleCtrlEvent 失败,并且 os.kill 调用 TerminateProcess.奇怪的是,如果您以系统"进程(PID 4)及其子进程 smss.exe(会话管理器)为目标,则调用会成功,但除了错误地将目标添加到附加进程列表(即 GetConsoleProcessList).这可能是因为父进程是空闲"进程,因为它是 PID 0,所以被隐式接受为广播 PGID.父进程规则也适用于非控制台进程.针对非控制台子进程没有任何作用——除了通过添加未附加的进程错误地破坏了控制台进程列表.我希望很清楚,您应该只向组 0 或通过 CREATE_NEW_PROCESS_GROUP 创建的已知进程组发送控制事件.

不要依赖于能够将 CTRL_C_EVENT 发送到组 0 以外的任何内容,因为它最初在新进程组中被禁用.将此事件发送到新组并非不可能,但目标进程首先必须通过调用 SetConsoleCtrlHandler(NULL, FALSE) 来启用 CTRL_C_EVENT.

CTRL_BREAK_EVENT 是您可以依赖的全部,因为它无法被禁用.发送此事件是一种优雅地终止以 CREATE_NEW_PROCESS_GROUP 启动的子进程的简单方法,假设它具有 Windows CTRL_BREAK_EVENT 或 C SIGBREAK 处理程序.如果没有,默认处理程序将终止进程,将退出代码设置为 STATUS_CONTROL_C_EXIT.例如:

<预><代码>>>>导入操作系统、信号、子进程>>>p = subprocess.Popen('python.exe',... stdin=subprocess.PIPE,... creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)>>>os.kill(p.pid,signal.CTRL_BREAK_EVENT)>>>STATUS_CONTROL_C_EXIT = 0xC000013A>>>p.wait() == STATUS_CONTROL_C_EXIT真的

请注意,CTRL_BREAK_EVENT 并未发送到当前进程,因为该示例针对的是子进程的进程组(包括附加到控制台的所有子进程,等等)).如果示例使用了组 0,则当前进程也会被终止,因为我没有定义 SIGBREAK 处理程序.让我们尝试一下,但要设置一个处理程序:

<预><代码>>>>ctrl_break = lambda *a: print('^BREAK')>>>信号.信号(信号.SIGBREAK,ctrl_break)<Handlers.SIG_DFL: 0>>>>os.kill(0,信号.CTRL_BREAK_EVENT)^打破

<小时>

[*]

Windows 具有异步过程调用 (APC) 将目标函数排队到线程.有关 Windows APC 的深入分析,请参阅Inside NT 的异步过程调用一文,特别是阐明内核模式 APC 的作用.您可以通过 QueueUserAPC 将用户模式 ​​APC 排队到线程.它们也会被 ReadFileExWriteFileEx 用于 I/O 完成例程.

用户模式 ​​APC 在线程进入警报等待时执行(例如 WaitForSingleObjectExSleepExbAlertableTRUE).另一方面,内核模式 APC 会立即被分派(当 IRQL 低于 APC_LEVEL 时).它们通常被 I/O 管理器用于在发出请求的线程的上下文中完成异步 I/O 请求数据包(例如,将数据从 IRP 复制到用户模式缓冲区).请参阅等待和 APC 以获取显示 APC 如何影响警报和非警报等待的表格.请注意,内核模式 APC 不会中断等待,而是由等待例程在内部执行.

Windows 可以使用 APC 实现类似 POSIX 的信号,但实际上它使用其他方法来达到相同的目的.例如:

窗口消息可以发送和发布到所有共享调用线程桌面的线程,并且处于相同或更低的完整性级别.当线程调用PeekMessageGetMessage.发布消息会将其添加到线程的消息队列中,该队列的默认配额为 10,000 条消息.带有消息队列的线程应该有一个消息循环来通过 GetMessage<代码>调度消息.仅控制台进程中的线程通常没有消息队列.但是,控制台主机进程 conhost.exe 显然可以.单击关闭按钮时,或者当控制台的主进程通过任务管理器或 taskkill.exe,一个 WM_CLOSE 消息被发送到控制台窗口线程的消息队列.控制台依次向其所有附加进程发送 CTRL_CLOSE_EVENT.如果一个进程处理了该事件,它会在 5 秒内正常退出,然后才会被强制终止.

I am trying the code pasted below on Windows, but instead of handling signal, it is killing the process. However, the same code is working in Ubuntu.

import os, sys
import time
import signal
def func(signum, frame):
    print 'You raised a SigInt! Signal handler called with signal', signum

signal.signal(signal.SIGINT, func)
while True:
    print "Running...",os.getpid()
    time.sleep(2)
    os.kill(os.getpid(),signal.SIGINT)

解决方案

Python's os.kill wraps two unrelated APIs on Windows. It calls GenerateConsoleCtrlEvent when the sig parameter is CTRL_C_EVENT or CTRL_BREAK_EVENT. In this case the pid parameter is a process group ID. If the latter call fails, and for all other sig values, it calls OpenProcess and then TerminateProcess. In this case the pid parameter is a process ID, and the sig value is passed as the exit code. Terminating a Windows process is akin to sending SIGKILL to a POSIX process. Generally this should be avoided since it doesn't allow the process to exit cleanly.

Note that the docs for os.kill mistakenly claim that "kill() additionally takes process handles to be killed", which was never true. It calls OpenProcess to get a process handle.

The decision to use WinAPI CTRL_C_EVENT and CTRL_BREAK_EVENT, instead of SIGINT and SIGBREAK, is unfortunate for cross-platform code. It's also not defined what GenerateConsoleCtrlEvent does when passed a process ID that's not a process group ID. Using this function in an API that takes a process ID is dubious at best, and potentially very wrong.

For your particular needs you can write an adapter function that makes os.kill a bit more friendly for cross-platform code. For example:

import os
import sys
import time
import signal

if sys.platform != 'win32':
    kill = os.kill
    sleep = time.sleep
else: 
    # adapt the conflated API on Windows.
    import threading

    sigmap = {signal.SIGINT: signal.CTRL_C_EVENT,
              signal.SIGBREAK: signal.CTRL_BREAK_EVENT}

    def kill(pid, signum):
        if signum in sigmap and pid == os.getpid():
            # we don't know if the current process is a
            # process group leader, so just broadcast
            # to all processes attached to this console.
            pid = 0
        thread = threading.current_thread()
        handler = signal.getsignal(signum)
        # work around the synchronization problem when calling
        # kill from the main thread.
        if (signum in sigmap and
            thread.name == 'MainThread' and
            callable(handler) and
            pid == 0):
            event = threading.Event()
            def handler_set_event(signum, frame):
                event.set()
                return handler(signum, frame)
            signal.signal(signum, handler_set_event)                
            try:
                os.kill(pid, sigmap[signum])
                # busy wait because we can't block in the main
                # thread, else the signal handler can't execute.
                while not event.is_set():
                    pass
            finally:
                signal.signal(signum, handler)
        else:
            os.kill(pid, sigmap.get(signum, signum))

    if sys.version_info[0] > 2:
        sleep = time.sleep
    else:
        import errno

        # If the signal handler doesn't raise an exception,
        # time.sleep in Python 2 raises an EINTR IOError, but
        # Python 3 just resumes the sleep.

        def sleep(interval):
            '''sleep that ignores EINTR in 2.x on Windows'''
            while True:
                try:
                    t = time.time()
                    time.sleep(interval)
                except IOError as e:
                    if e.errno != errno.EINTR:
                        raise
                interval -= time.time() - t
                if interval <= 0:
                    break

def func(signum, frame):
    # note: don't print in a signal handler.
    global g_sigint
    g_sigint = True
    #raise KeyboardInterrupt

signal.signal(signal.SIGINT, func)

g_kill = False
while True:
    g_sigint = False
    g_kill = not g_kill
    print('Running [%d]' % os.getpid())
    sleep(2)
    if g_kill:
        kill(os.getpid(), signal.SIGINT)
    if g_sigint:
        print('SIGINT')
    else:
        print('No SIGINT')

Discussion

Windows doesn't implement signals at the system level [*]. Microsoft's C runtime implements the six signals that are required by standard C: SIGINT, SIGABRT, SIGTERM, SIGSEGV, SIGILL, and SIGFPE.

SIGABRT and SIGTERM are implemented just for the current process. You can call the handler via C raise. For example (in Python 3.5):

>>> import signal, ctypes
>>> ucrtbase = ctypes.CDLL('ucrtbase')
>>> c_raise = ucrtbase['raise']
>>> foo = lambda *a: print('foo')
>>> signal.signal(signal.SIGTERM, foo)
<Handlers.SIG_DFL: 0>
>>> c_raise(signal.SIGTERM)
foo
0

SIGTERM is useless.

You also can't do much with SIGABRT using the signal module because the abort function kills the process once the handler returns, which happens immediately when using the signal module's internal handler (it trips a flag for the registered Python callable to be called in the main thread). For Python 3 you can instead use the faulthandler module. Or call the CRT's signal function via ctypes to set a ctypes callback as the handler.

The CRT implements SIGSEGV, SIGILL, and SIGFPE by setting a Windows structured exception handler for the corresponding Windows exceptions:

STATUS_ACCESS_VIOLATION          SIGSEGV
STATUS_ILLEGAL_INSTRUCTION       SIGILL
STATUS_PRIVILEGED_INSTRUCTION    SIGILL
STATUS_FLOAT_DENORMAL_OPERAND    SIGFPE
STATUS_FLOAT_DIVIDE_BY_ZERO      SIGFPE
STATUS_FLOAT_INEXACT_RESULT      SIGFPE
STATUS_FLOAT_INVALID_OPERATION   SIGFPE
STATUS_FLOAT_OVERFLOW            SIGFPE
STATUS_FLOAT_STACK_CHECK         SIGFPE
STATUS_FLOAT_UNDERFLOW           SIGFPE
STATUS_FLOAT_MULTIPLE_FAULTS     SIGFPE
STATUS_FLOAT_MULTIPLE_TRAPS      SIGFPE

The CRT's implementation of these signals is incompatible with Python's signal handling. The exception filter calls the registered handler and then returns EXCEPTION_CONTINUE_EXECUTION. However, Python's handler only trips a flag for the interpreter to call the registered callable sometime later in the main thread. Thus the errant code that triggered the exception will continue to trigger in an endless loop. In Python 3 you can use the faulthandler module for these exception-based signals.

That leaves SIGINT, to which Windows adds the non-standard SIGBREAK. Both console and non-console processes can raise these signals, but only a console process can receive them from another process. The CRT implements this by registering a console control event handler via SetConsoleCtrlHandler.

The console sends a control event by creating a new thread in an attached process that begins executing at CtrlRoutine in kernel32.dll or kernelbase.dll (undocumented). That the handler doesn't execute on the main thread can lead to synchronization problems (e.g. in the REPL or with input). Also, a control event won't interrupt the main thread if it's blocked while waiting on a synchronization object or waiting for synchronous I/O to complete. Care needs to be taken to avoid blocking in the main thread if it should be interruptible by SIGINT. Python 3 attempts to work around this by using a Windows event object, which can also be used in waits that should be interruptible by SIGINT.

When the console sends the process a CTRL_C_EVENT or CTRL_BREAK_EVENT, the CRT's handler calls the registered SIGINT or SIGBREAK handler, respectively. The SIGBREAK handler is also called for the CTRL_CLOSE_EVENT that the console sends when its window is closed. Python defaults to handling SIGINT by rasing a KeyboardInterrupt in the main thread. However, SIGBREAK is initially the default CTRL_BREAK_EVENT handler, which calls ExitProcess(STATUS_CONTROL_C_EXIT).

You can send a control event to all processes attached to the current console via GenerateConsoleCtrlEvent. This can target a subset of processes that belong to a process group, or target group 0 to send the event to all processes attached to the current console.

Process groups aren't a well-documented aspect of the Windows API. There's no public API to query the group of a process, but every process in a Windows session belongs to a process group, even if it's just the wininit.exe group (services session) or winlogon.exe group (interactive session). A new group is created by passing the creation flag CREATE_NEW_PROCESS_GROUP when creating a new process. The group ID is the process ID of the created process. To my knowledge, the console is the only system that uses the process group, and that's just for GenerateConsoleCtrlEvent.

What the console does when the target ID isn't a process group ID is undefined and should not be relied on. If both the process and its parent process are attached to the console, then sending it a control event basically acts like the target is group 0. If the parent process isn't attached to the current console, then GenerateConsoleCtrlEvent fails, and os.kill calls TerminateProcess. Weirdly, if you target the "System" process (PID 4) and its child process smss.exe (session manager), the call succeeds but nothing happens except that the target is mistakenly added to the list of attached processes (i.e. GetConsoleProcessList). It's probably because the parent process is the "Idle" process, which, since it's PID 0, is implicitly accepted as the broadcast PGID. The parent process rule also applies to non-console processes. Targeting a non-console child process does nothing -- except mistakenly corrupt the console process list by adding the unattached process. I hope it's clear that you should only send a control event to either group 0 or to a known process group that you created via CREATE_NEW_PROCESS_GROUP.

Don't rely on being able to send CTRL_C_EVENT to anything but group 0, since it's initially disabled in a new process group. It's not impossible to send this event to a new group, but the target process first has to enable CTRL_C_EVENT by calling SetConsoleCtrlHandler(NULL, FALSE).

CTRL_BREAK_EVENT is all you can depend on since it can't be disabled. Sending this event is a simple way to gracefully kill a child process that was started with CREATE_NEW_PROCESS_GROUP, assuming it has a Windows CTRL_BREAK_EVENT or C SIGBREAK handler. If not, the default handler will terminate the process, setting the exit code to STATUS_CONTROL_C_EXIT. For example:

>>> import os, signal, subprocess
>>> p = subprocess.Popen('python.exe',
...         stdin=subprocess.PIPE,
...         creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
>>> os.kill(p.pid, signal.CTRL_BREAK_EVENT)
>>> STATUS_CONTROL_C_EXIT = 0xC000013A
>>> p.wait() == STATUS_CONTROL_C_EXIT
True

Note that CTRL_BREAK_EVENT wasn't sent to the current process, because the example targets the process group of the child process (including all of its child processes that are attached to the console, and so on). If the example had used group 0, the current process would have been killed as well since I didn't define a SIGBREAK handler. Let's try that, but with a handler set:

>>> ctrl_break = lambda *a: print('^BREAK')
>>> signal.signal(signal.SIGBREAK, ctrl_break)
<Handlers.SIG_DFL: 0>
>>> os.kill(0, signal.CTRL_BREAK_EVENT)
^BREAK


[*]

Windows has asynchronous procedure calls (APC) to queue a target function to a thread. See the article Inside NT's Asynchronous Procedure Call for an in-depth analysis of Windows APCs, especially to clarify the role of kernel-mode APCs. You can queue a user-mode APC to a thread via QueueUserAPC. They also get queued by ReadFileEx and WriteFileEx for the I/O completion routine.

A user-mode APC executes when the thread enters an alertable wait (e.g. WaitForSingleObjectEx or SleepEx with bAlertable as TRUE). Kernel-mode APCs, on the other hand, get dispatched immediately (when the IRQL is below APC_LEVEL). They're typically used by the I/O manager to complete asynchronous I/O Request Packets in the context of the thread that issued the request (e.g. copying data from the IRP to a user-mode buffer). See Waits and APCs for a table that shows how APCs affect alertable and non-alertable waits. Note that kernel-mode APCs don't interrupt a wait, but instead are executed internally by the wait routine.

Windows could implement POSIX-like signals using APCs, but in practice it uses other means for the same ends. For example:

Window messages can be sent and posted to all threads that share the calling thread's desktop and that are at the same or lower integrity level. Sending a window message puts it in a system queue to call the window procedure when the thread calls PeekMessage or GetMessage. Posting a message adds it to the thread's message queue, which has a default quota of 10,000 messages. A thread with a message queue should have a message loop to process the queue via GetMessage and DispatchMessage. Threads in a console-only process typically do not have a message queue. However, the console host process, conhost.exe, obviously does. When the close button is clicked, or when the primary process of a console is killed via the task manager or taskkill.exe, a WM_CLOSE message is posted to the message queue of the console window's thread. The console in turns sends a CTRL_CLOSE_EVENT to all of its attached processes. If a process handles the event, it's given 5 seconds to exit gracefully before it's forcefully terminated.

这篇关于如何在windows机器上处理python中的信号的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆