首页 > 解决方案 > 如何制作可以由后台线程终止的 Python CLI input() 循环?

问题描述

编辑:澄清这适用于 Python 3。SO 上的其他一些帖子适用于 Python 2,不适用于 Python 3。

我正在用 Python 编写一个机器人。该机器人将启动一些后台线程来执行诸如轮询服务、执行 API 调用等操作。该机器人的主线程是一个 CLI,它实现了一种“REPL”循环(输入命令和参数,会发生一些事情,等待另一个命令,冲洗并重复。)

机器人需要终止的情况有两种:

  1. 如果用户键入退出命令。在这种情况下,主循环将设置标志告诉处理线程终止并加入这些线程;一旦所有线程终止,应用程序就会正确退出。
  2. 如果其中一个处理线程决定机器人需要终止。例如,如果管理员通过外部界面而不是通过 CLI 请求机器人断开连接。

这是一些非常粗略的伪代码:

import threading

class Bot:

    KeepRunning = True 

    def api_thread(self):
        # we can assume this loop will execute at least once per second or so
        while self.KeepRunning:
            shouldQuit = pollTheApiAndDoThings()
            if shouldQuit:
                self.KeepRunning = False
                return
    
    def service_thread(self):
        # we can assume this loop will execute at least once per second or so
        while self.KeepRunning:
            shouldQuit = connectToAServiceAndDoThings()
            if shouldQuit:
                self.KeepRunning = False
                return

    def stop(self):
        self.KeepRunning = False
        self._api_thread.join()
        self._service_thread.join()

    def run(self):
        self._api_thread = threading.Thread(target=self.api_thread)
        self._service_thread = threading.Thread(target=self.service_thread)
        self._api_thread.start()
        self._service_thread.start()

if __name__ == "__main__":
    b = Bot()
    b.run()

    while b.KeepRunning:
        cmd = input("> ")
        if cmd.lower().rstrip()=="quit":
            b.stop() # wait for bot to stop
            exit(0)
        elif ... # process all other user commands

    # if keepRunning got set to false while we were in the input loop, don't repeat the loop.
    # however, we want some way to end the input() call immediately if keepRunning gets set false.
    print("Remote shutdown requested. Exiting now.")
    exit(0)

如果用户键入quit. 每个线程将在其下一次迭代中退出,机器人将退出。

但是,如果机器人线程之一需要告诉机器人关闭,程序将保持运行,直到用户至少按一次 Enter。此外,由于机器人已经基本终止,任何命令都需要被忽略。

如果没有 CLI,那么此应用程序将正常工作(例如,仅用 time.sleep(1) 替换输入循环,以便主线程保持活动状态,直到机器人线程请求退出)。所以,从逻辑上讲,我需要的是一种让input()函数类似于循环运行的方式,该循环具有睡眠然后检查标志的想法。

我看过其他关于在 上执行超时的帖子input(),但这里的不同之处在于没有特定的超时。只要机器人正在运行,我们就可以input()根据需要阻止。但是一旦其中一个线程发出退出信号,就input()需要取消调用。即使用户已经输入了一些内容,也可以忽略它,因为机器人已经关闭了。

这需要能够在具有相同代码的 Windows 和基于 Unix 的操作系统上工作。platform用或类似的方式检查操作系统当然是可以接受的。

我考虑过使用像这样的方法并编写我自己的版本,input()但这似乎非常麻烦且容易出错,尤其是跨平台。它还需要包含一个读取超时,这样它就不会变得与 相同input(),而且我还需要手动编写诸如 Ctrl+C 处理之类的代码——不理想。而且它还会阻止通过标准输入传递字符串,例如使用子进程 - 因为据我所知,如果你正在进行原始终端交互(?)

标签: pythonmultithreading

解决方案


推荐阅读