我知道这已经很旧了,但我遇到了完全相同的问题,并且需要在Docker(ubuntu 20.04)和Windows上使Ctrl-C行为正常工作。特别是在Windows上,信号处理只在主线程上进行,在线程不处于等待状态时才会执行。对于try: except KeyboardInterrupt:以及signal.signal(signal.SIGINT, handler)两种情况,只有当主线程不处于等待状态时才会触发或调用。
例如,如果您将代码更改为以下内容,并在中途按下Ctrl-C,您将看到异常被捕获,但仅在reqThread实际终止并且thread.join()返回时才会发生。
import threading, time
class reqthread(threading.Thread):
def run(self):
for i in range(0, 10):
time.sleep(1)
print('.')
try:
thread = reqthread()
thread.start()
thread.join()
except (KeyboardInterrupt, SystemExit):
print('\n! Received keyboard interrupt, quitting threads.\n')
然而,有趣的是,当主线程运行一个asyncio循环时,在Windows和Linux上(至少在我正在使用的docker Ubuntu镜像上),它总是能够捕获到Ctrl-C信号。
以下代码段演示了这种行为。
import threading, time, signal, asyncio
localLoop = asyncio.new_event_loop()
syncShutdownEvent = threading.Event()
class reqthread(threading.Thread):
def run(self):
for i in range(0, 10):
time.sleep(1)
print('.')
if syncShutdownEvent.is_set():
break
print("reqthread stopped")
done()
return
def done():
localLoop.call_soon_threadsafe(lambda: localLoop.stop())
def handler(signum, frame):
signal.getsignal(signum)
print(f'\n! Received signal {signal.Signals(signum).name}, quitting threads.\n')
syncShutdownEvent.set()
def hookKeyboardInterruptSignals():
for selectSignal in [x for x in signal.valid_signals() if isinstance(x, signal.Signals) and x.name in ('SIGINT', 'SIGBREAK')]:
signal.signal(selectSignal.value, handler)
hookKeyboardInterruptSignals()
thread = reqthread()
thread.start()
asyncio.set_event_loop(localLoop)
localLoop.run_forever()
localLoop.close()
并且将在Windows和Ubuntu上具有相同的行为
python scratch_14.py
.
.
! Received keyboard interrupt, quitting threads.
.
reqthread stopped
对于我的特定应用,需要使用一个运行同步代码的线程和一个运行异步代码的线程,我实际上使用了总共三个线程。
- 主线程,运行Ctrl-C asyncio拦截器
- 同步线程
- Asyncio循环线程
编辑:修复了一个错别字,导致第一个代码块的import语句被解释为普通文本而不是代码块的一部分
join
上设置超时,即while thread.isAlive: thread.join(5)
,也可以使主线程对异常保持响应。 - Dr. Jan-Philip Gehrckethread.daemon = True
实际上不建议使用,因为它会防止线程清理任何遗留的资源... - Erik Kaplun