线程忽略键盘中断异常

发布于 2024-09-24 18:59:52 字数 732 浏览 5 评论 0原文

我正在运行这个简单的代码:

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()
except (KeyboardInterrupt, SystemExit):
    print('\n! Received keyboard interrupt, quitting threads.\n')

但是当我运行它时,它会打印

$ python prova.py
.
.
^C.
.
.
.
.
.
.
.
Exception KeyboardInterrupt in <module 'threading' from '/usr/lib/python2.6/threading.pyc'> ignored

In fact python threadignore my Ctrl+C Keyboard Interrupt 并且不会打印 Received键盘中断。为什么?这段代码有什么问题?

I'm running this simple code:

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()
except (KeyboardInterrupt, SystemExit):
    print('\n! Received keyboard interrupt, quitting threads.\n')

But when I run it, it prints

$ python prova.py
.
.
^C.
.
.
.
.
.
.
.
Exception KeyboardInterrupt in <module 'threading' from '/usr/lib/python2.6/threading.pyc'> ignored

In fact python thread ignore my Ctrl+C keyboard interrupt and doesn't print Received Keyboard Interrupt. Why? What is wrong with this code?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

夏九 2024-10-01 18:59:52

Try

try:
    thread = reqthread()
    thread.daemon = True
    thread.start()
    while True:
        time.sleep(100)
except (KeyboardInterrupt, SystemExit):
    print('Received keyboard interrupt, quitting threads.')
 

如果没有调用 time.sleep,主进程会过早地跳出 try... except 块,因此 KeyboardInterrupt没有被抓住。我的第一个想法是使用 thread.join,但这似乎会阻塞主进程(忽略键盘中断),直到线程完成。

thread.daemon=True 导致线程在主进程结束时终止。

Try

try:
    thread = reqthread()
    thread.daemon = True
    thread.start()
    while True:
        time.sleep(100)
except (KeyboardInterrupt, SystemExit):
    print('Received keyboard interrupt, quitting threads.')
 

Without the call to time.sleep, the main process is jumping out of the try...except block too early, so the KeyboardInterrupt is not caught. My first thought was to use thread.join, but that seems to block the main process (ignoring KeyboardInterrupt) until the thread is finished.

thread.daemon=True causes the thread to terminate when the main process ends.

野の 2024-10-01 18:59:52

总结评论,以下内容对我来说效果很好:

try:
  thread = reqthread()
  thread.start()
  while thread.isAlive(): 
    thread.join(1)  # not sure if there is an appreciable cost to this.
except (KeyboardInterrupt, SystemExit):
  print '\n! Received keyboard interrupt, quitting threads.\n'
  sys.exit()

To summarize the changes recommended in the comments, the following works well for me:

try:
  thread = reqthread()
  thread.start()
  while thread.isAlive(): 
    thread.join(1)  # not sure if there is an appreciable cost to this.
except (KeyboardInterrupt, SystemExit):
  print '\n! Received keyboard interrupt, quitting threads.\n'
  sys.exit()
听风念你 2024-10-01 18:59:52

对 Ubuntu 的解决方案稍加修改。

按照 Eric 的建议删除 tread.daemon = True 并用 signal.pause() 替换睡眠循环:

import signal

try:
  thread = reqthread()
  thread.start()
  signal.pause()  # instead of: while True: time.sleep(100)
except (KeyboardInterrupt, SystemExit):
  print('Received keyboard interrupt, quitting threads.)

Slight modification of Ubuntu's solution.

Removing tread.daemon = True as suggested by Eric and replacing the sleeping loop by signal.pause():

import signal

try:
  thread = reqthread()
  thread.start()
  signal.pause()  # instead of: while True: time.sleep(100)
except (KeyboardInterrupt, SystemExit):
  print('Received keyboard interrupt, quitting threads.)
与之呼应 2024-10-01 18:59:52

我的(hacky)解决方案是对 Thread.join() 进行猴子补丁,如下所示:

def initThreadJoinHack():
  import threading, thread
  mainThread = threading.currentThread()
  assert isinstance(mainThread, threading._MainThread)
  mainThreadId = thread.get_ident()
  join_orig = threading.Thread.join
  def join_hacked(threadObj, timeout=None):
    """
    :type threadObj: threading.Thread
    :type timeout: float|None
    """
    if timeout is None and thread.get_ident() == mainThreadId:
      # This is a HACK for Thread.join() if we are in the main thread.
      # In that case, a Thread.join(timeout=None) would hang and even not respond to signals
      # because signals will get delivered to other threads and Python would forward
      # them for delayed handling to the main thread which hangs.
      # See CPython signalmodule.c.
      # Currently the best solution I can think of:
      while threadObj.isAlive():
        join_orig(threadObj, timeout=0.1)
    else:
      # In all other cases, we can use the original.
      join_orig(threadObj, timeout=timeout)
  threading.Thread.join = join_hacked

My (hacky) solution is to monkey-patch Thread.join() like this:

def initThreadJoinHack():
  import threading, thread
  mainThread = threading.currentThread()
  assert isinstance(mainThread, threading._MainThread)
  mainThreadId = thread.get_ident()
  join_orig = threading.Thread.join
  def join_hacked(threadObj, timeout=None):
    """
    :type threadObj: threading.Thread
    :type timeout: float|None
    """
    if timeout is None and thread.get_ident() == mainThreadId:
      # This is a HACK for Thread.join() if we are in the main thread.
      # In that case, a Thread.join(timeout=None) would hang and even not respond to signals
      # because signals will get delivered to other threads and Python would forward
      # them for delayed handling to the main thread which hangs.
      # See CPython signalmodule.c.
      # Currently the best solution I can think of:
      while threadObj.isAlive():
        join_orig(threadObj, timeout=0.1)
    else:
      # In all other cases, we can use the original.
      join_orig(threadObj, timeout=timeout)
  threading.Thread.join = join_hacked
梨涡少年 2024-10-01 18:59:52

try ... except 放入每个线程中,并将 signal.pause() 放入 true main() 对我有用。

不过请注意导入锁。我猜这就是为什么Python默认不解决ctrl-C的原因。

Putting the try ... except in each thread and also a signal.pause() in true main() works for me.

Watch out for import lock though. I am guessing this is why Python doesn't solve ctrl-C by default.

三人与歌 2024-10-01 18:59:52

我知道这已经很老了,但我遇到了这个确切的问题,并且需要 Ctrl-C 行为才能在 Docker (ubuntu 20.04) 和 Windows 上工作。特别是在 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 上捕获 Ctrl-C(至少在我运行的 docker Ubuntu 映像上)。

下面的代码演示了该行为,

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

,其中需要使用 1 个运行同步代码的线程和 1 个运行异步代码的线程,我实际上总共使用了三个线程。

  1. 主线程,运行 Ctrl-C 异步捕获器
  2. 同步线程
  3. 异步循环线程

编辑:修复了导致第一个代码块导入语句被解释为纯文本而不是代码块的一部分的拼写错误

I know this is quite old but I had this exact issue and required the Ctrl-C behavior to work on Docker (ubuntu 20.04) and on Windows. On windows specifically, the signal handling is done only onto the main thread, only when the thread is not in a wait state. This is both true for a try: except KeyboardInterrupt: and for a signal.signal(signal.SIGINT, handler) where either gets raised or called only when the main thread is out of a wait.

For instance if you change your code to the following and press Ctrl-C midway, you will see that the exception gets caught but only when reqThread actually terminates and therefore thread.join() returns.

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')

However, an interesting thing is that when the main thread is running an asyncio loop, it will always catch a Ctrl-C on both Windows and Linux (at least on the docker Ubuntu image I am running).

the following piece of code demonstrates the behavior

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()

and will give you the same behavior on both Windows and Ubuntu

python scratch_14.py
.
.

! Received keyboard interrupt, quitting threads.

.
reqthread stopped

for my specific application where is need to use 1 thread running synchronous code and 1 thread running async code i actually use a total of three threads.

  1. Main thread, running the Ctrl-C asyncio catcher
  2. Synchronous thread
  3. Asyncio loop thread

EDIT: fixed a typo that caused the first code block import statement to be interpreted as plain text instead of part of the code block

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文