【问题标题】:Rationalizing and simplifying Python 3 Keyboard Interrupt behavior in a threaded program合理化和简化线程程序中的 Python 3 键盘中断行为
【发布时间】:2020-07-05 19:11:43
【问题描述】:

运行以下最小化且可重现的代码示例,python(例如 3.7.3 和 3.8.3)将在第一个 Ctrl+C 时发出如下消息被按下,而不是终止程序。

Traceback (most recent call last):
  File "main.py", line 44, in <module>
    Main()
  File "main.py", line 41, in __init__
    self.interaction_manager.join()
  File "/home/user/anaconda3/lib/python3.7/threading.py", line 1032, in join
    self._wait_for_tstate_lock()
  File "/home/user/anaconda3/lib/python3.7/threading.py", line 1048, in _wait_for_tstate_lock
    elif lock.acquire(block, timeout):
KeyboardInterrupt

只有在此之后再次按下 Ctrl+C 时,程序才会终止。

这种设计背后的基本原理是什么?避免需要多个 Ctrl+C 或底层信号的优雅方法是什么?

代码如下:

from threading import Thread
from queue import Queue, Empty

def get_event(queue: Queue, block=True, timeout=None):
    """ just a convenience wrapper for avoiding try-except clutter in code """
    try:
        element = queue.get(block, timeout)
    except Empty:
        element = Empty

    return element


class InteractionManager(Thread):

    def __init__(self):
        super().__init__()
        self.queue = Queue()

    def run(self):
        while True:
            event = get_event(self.queue, block=True, timeout=0.1)


class Main(object):

    def __init__(self):

        # kick off the user interaction
        self.interaction_manager = InteractionManager()
        self.interaction_manager.start()

        # wait for the interaction manager object shutdown as a signal to shutdown
        self.interaction_manager.join()


if __name__ == "__main__":
    Main()

史前相关问题:Interruptible thread join in Python

【问题讨论】:

  • 如您所说,这可能是为了防止您意外取消您可能不想取消的内容。
  • Main "class" 是我最近看到的对 OOP 的最严重滥用......
  • 你永远不会费心去关闭线程。杀死主线程后,Python 仍然等待InteractionManager 线程停止。 double [Ctrl]+[C] 的 full 回溯立即揭示了这一点。如果您不希望它逗留,请使用 daemon 线程。
  • 感谢主课评论,真的很有帮助
  • 谢谢@MisterMiyagi,我明白了一般的想法,尽管第二个回溯如何揭示它,也不知道在第二个 ctrl-c 或什么之后,连接函数如何仍然存在否则使第二个回溯提到连接功能。

标签: python python-3.x


【解决方案1】:

Python 在退出前等待所有非daemon 线程。第一个 Ctrl+C 只杀死显式 self.interaction_manager.join(),第二个 Ctrl+C 杀死内部 @ 987654323@ 的threading。要么将线程声明为可消耗的daemon 线程,要么发出信号关闭。

可以通过将daemon=True 设置为关键字或属性来将线程声明为可消耗的:

class InteractionManager(Thread):
    def __init__(self):
        super().__init__(daemon=True)
        self.queue = Queue()

    def run(self):
        while True:
            event = get_event(self.queue, block=True, timeout=0.1)

daemon 线程被突然终止,如果它持有任何资源,可能无法干净地释放资源。

可以使用共享标志来协调正常关闭,例如threading.Event 或布尔值:

shutdown = False

class InteractionManager(Thread):
    def __init__(self):
        super().__init__()
        self.queue = Queue()

    def run(self):
        while not shutdown:
            event = get_event(self.queue, block=True, timeout=0.1)

def main()
    self.interaction_manager = InteractionManager()
    self.interaction_manager.start()
    try:
        self.interaction_manager.join()
    finally:
        global shutdown
        shutdown = True

【讨论】:

    猜你喜欢
    • 2014-08-11
    • 2018-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    相关资源
    最近更新 更多