使用threading.Timer时,Ctrl-C不起作用

时间:2011-07-06 09:34:13

标签: python multithreading timer

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

我曾经使用ctrl-c终止该应用,但是一旦我添加threading.Timer个实例ctrl-c停止工作(或者有时需要很长时间)。

怎么会这样? 定时器线程与ctrl-c之间的关系是什么?

更新
我在Python的thread documentation中找到了以下内容:

  

线程奇怪地与之交互   中断:KeyboardInterrupt   将收到例外情况   任意线程。 (当信号   模块可用,始终中断   转到主线程。)

4 个答案:

答案 0 :(得分:4)

threading.Thread(以及threading.Timer)的工作方式是每个线程向threading模块注册自己,并在解释器退出时,解释器将等待所有注册的线程退出之前正确终止翻译。这样做是为了让线程实际完成执行,而不是将解释器从它们下面粗暴地删除。所以当你点击^ C时,主线程收到信号,决定终止并等待定时器完成。

您可以设置线程 daemonic (使用setDaemon方法)以使线程模块不等待这些线程,但如果它们恰好在解释器退出时执行Python代码,退出时会出现混乱的错误。即使您取消threading.Timer(并设置它的守护程序),它仍然可以在解释器被销毁时唤醒 - 因为threading.Timer的{​​{1}}方法只是告诉{{1}它在唤醒时不执行任何操作,但它必须实际执行Python代码才能做出决定。

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

答案 1 :(得分:2)

David Beazley的演讲揭示了这个话题。 PDF可用here。查看第22--25页(“Interlude:Signals”到“Frozen Signals”)。

答案 2 :(得分:0)

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

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

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)

这个 while True: 机制看起来有点笨拙。但我认为,正如我所说,目前(Python 3.8.x)KeyboardInterrupt 只能在主线程上被捕获。

PS 根据我的实验,处理子进程可能更容易,因为至少在一个简单的情况下,Ctrl-C 似乎会导致 KeyboardInterrupt 在所有正在运行的进程中同时发生。

答案 3 :(得分:0)

将你的主要 while 循环包裹在一个 try 中,除了:

from threading import Timer
import time

def randomfn():
    print ("Heartbeat sent!")

class RepeatingTimer(Timer):
    def run(self):
        while not self.finished.is_set():
            self.function(*self.args, **self.kwargs)
            self.finished.wait(self.interval)

t = RepeatingTimer(10.0, function=randomfn)

print ("Starting...")
t.start()

while (True):
    try:
        print ("Hello")
        time.sleep(1)
    except:
        print ("Cancelled timer...")
        t.cancel()
        print ("Cancelled loop...")
        break

print ("End")

结果:

Heartbeat sent!
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Cancelled timer...
Cancelled loop...
End