Python Ctrl-C不';使用线程时不工作。计时器

Python Ctrl-C不';使用线程时不工作。计时器,python,multithreading,timer,Python,Multithreading,Timer,我正在Windows上编写一个多线程Python应用程序 我曾经使用ctrl-c终止应用程序,但一旦我添加了线程。计时器实例ctrl-c停止工作(有时需要很长时间) 这怎么可能呢? 拥有计时器线程和ctrl-c之间有什么关系 更新: 我在Python中发现了以下内容: 线程与其他线程发生奇怪的交互 中断:键盘中断 例外情况将由 任意线程。(当信号发出时) 模块可用,始终中断 转到主线程。) threading.Thread(因此threading.Timer)的工作方式是每个线程向threadi

我正在Windows上编写一个多线程Python应用程序

我曾经使用
ctrl-c
终止应用程序,但一旦我添加了
线程。计时器
实例
ctrl-c
停止工作(有时需要很长时间)

这怎么可能呢?
拥有计时器线程和
ctrl-c
之间有什么关系

更新:
我在Python中发现了以下内容:

线程与其他线程发生奇怪的交互 中断:键盘中断 例外情况将由 任意线程。(当信号发出时) 模块可用,始终中断 转到主线程。)


threading.Thread
(因此
threading.Timer
)的工作方式是每个线程向
threading
模块注册自己,解释器退出后,解释器将等待所有注册的线程退出,然后再正确终止解释器。这样做是为了让线程真正完成执行,而不是让解释器从它们下面被无情地移除。因此,当您点击^C时,主线程接收信号,决定终止并等待计时器完成

您可以设置线程守护进程(使用
setDaemon
方法),使线程模块不等待这些线程,但如果它们在解释器退出时碰巧正在执行Python代码,则在退出时会出现令人困惑的错误。即使取消
threading.Timer
(并将其设置为daemonic),它仍然可以在解释器被销毁时唤醒,因为
threading.Timer
cancel
方法只是告诉
threading.Timer
在唤醒时不要执行任何操作,但它必须实际执行Python代码才能做出决定


没有优雅的方式终止线程(当前线程除外),也没有可靠的方式中断被阻塞的线程。更易于管理的计时器方法通常是事件循环,就像GUI和其他事件驱动系统提供给您的那样。使用什么完全取决于您的程序将执行的其他操作。

大卫·比兹利(David Beazley)的一篇演讲对该主题有一些启发。PDF可供使用。查看第22-25页(“插曲:信号”到“冻结信号”)。

这是一种可能的解决方法:使用
time.sleep()
而不是
Timer
意味着可以实现“优雅关机”机制。。。对于Python3,其中显示,
KeyboardInterrupt
仅在主线程的用户代码中引发。否则,异常似乎被“忽略”了:事实上,它会导致发生异常的线程立即死亡,但不会导致任何祖先线程死亡,因为在这些线程中无法捕获异常

假设您希望Ctrl-C响应为0.5秒,但您只希望每5秒重复一些实际工作(工作的持续时间如下所示):

虽然正确:
机制看起来有点笨重。但我认为,正如我所说,目前(Python3.8.x)
KeyboardInterrupt
只能在主线程上捕获


PS根据我的实验,处理子进程可能更容易,从某种意义上说,至少在一个简单的情况下,Ctrl-C会导致
键盘中断
在所有正在运行的进程中同时发生。

您是否可以将代码粘贴到此处?感谢您提供了更大的视图。目前,我将解决问题的线程标记为daemonic,但我想在某个时候,我必须进行重构以获得正确的终止流。如果计时器设置在同一线程中,是否会执行信号处理程序?如果是,如果可以在信号处理程序中设置timer.Cancel,进程现在可以退出吗?+1。是否有一些资源可以解释为什么“没有优雅的方式终止线程(当前线程除外),也没有可靠的方式中断被阻止的线程”?请参阅我的答案。将
threading.Event
附加到
threading.Thread
是完全可能的,并且似乎使“优雅关闭”完全可能。或者我的解决方案有什么根本性的问题吗?这确实是一个有见地的PDF!
import threading, sys, time, random

blip_counter = 0
work_threads=[]
def repeat_every_5():
    global blip_counter
    print( f'counter: {blip_counter}')
    
    def real_work():
        real_work_duration_s = random.randrange(10)
        print( f'do some real work every 5 seconds, lasting {real_work_duration_s} s: starting...')
        # in a real world situation stop_event.is_set() can be tested anywhere in the code
        for interval_500ms in range( real_work_duration_s * 2 ):
            if threading.current_thread().stop_event.is_set():
                print( f'stop_event SET!')
                return
            time.sleep(0.5)
        print( f'...real work ends')
        # clean up work_threads as appropriate
        for work_thread in work_threads:
            if not work_thread.is_alive():
                print(f'work thread {work_thread} dead, removing from list' )
                work_threads.remove( work_thread )
                
    new_work_thread = threading.Thread(target=real_work)
    # stop event for graceful shutdown
    new_work_thread.stop_event = threading.Event()
    work_threads.append(new_work_thread)
    # in fact, because a graceful shutdown is now implemented, new_work_thread doesn't have to be daemon
    # new_work_thread.daemon = True
    new_work_thread.start()
    
    blip_counter += 1
    time.sleep( 5 )
    timer_thread = threading.Thread(target=repeat_every_5)
    timer_thread.daemon = True
    timer_thread.start()
repeat_every_5()

while True:
    try:
        time.sleep( 0.5 )
    except KeyboardInterrupt:
        print( f'shutting down due to Ctrl-C..., work threads left: {len(work_threads)}')
        # trigger stop event for graceful shutdown
        for work_thread in work_threads:
            if work_thread.is_alive():
                print( f'work_thread {work_thread}: setting STOP event')
                work_thread.stop_event.set()
                print( f'work_thread {work_thread}: joining to main...')
                work_thread.join()
                print( f'work_thread {work_thread}: ...joined to main')
            else:
                print( f'work_thread {work_thread} has died' )
        sys.exit(1)