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