使用带有多线程的 input() 优雅的循环退出

Elegant loop exit using input() with multithreading

提问人:Yegor Kozhevnikov 提问时间:11/12/2023 更新时间:11/12/2023 访问量:56

问:

我在一个程序中有一个函数,它是由重复“计数”次数的 for 循环实现的。我需要能够随时通过在控制台中键入“停止”来中断循环。我使用两个线程实现了这一点 - 一个线程使用循环初始化函数:

    def send_messages(count_msg: int, delay_msg: int):
        global stop_flag
        for _ in range(count_msg):
            if stop_flag:  # Boolean variable responsible for stopping the loop
                print('---Sending completed by the user [ОК]---')
                break
            message(messageText)  # Function responsible for sending a message
            time.sleep(delay_msg)
        if stop_flag:
            stop_flag = False  # Change the flag back so that the function can be called again

另一个线程初始化一个函数,该函数通过 input() 等待用户输入。

    def monitor_input():
        global stop_flag 
        user_input = input()
        if user_input == 'stop':
            stop_flag = True  # Change the flag to stop the sending function


send_thread = threading.Thread(target=send_messages, args=my_args)
stop_thread = threading.Thread(target=monitor_input)
send_threading.start()
stop_threading.start()

一切正常,但有一个例外:如果循环没有中断,而只是等待其完成,则函数仍在等待用户输入,并且不方便关闭程序,更准确地说,出现错误:UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte

我希望我能解决这个问题

我想解决这个问题,而不是为了我自己,因为我已经找到了使用 msvcrt 模块的解决方案,但它仅适用于 Windows,我想找到一种更“灵活”的方式来做到这一点,因为我只是在学习编程

我试图通过错误处理来做到这一点:

    def monitor_input():
        global stop_flag
        try:
            user_input = input()
        except UnicodeDecodeError as e:
            user_input = ''
            print('Terminated by user [OK]')  
            sys.exit(1)
        if user_input == 'stop':
            stop_flag = True  # Change the flag to stop the sending function

这奏效了,但不是很好,因为程序必须“两次”关闭,也就是说,按下终止按钮后它不会终止。据我了解,这个问题是由于一个线程仍然处于活动状态。

我知道这是一个非常小的问题,但我想解决它并学习一些新东西,所以我非常感谢您的帮助!

Python 多线程 循环 终止

评论


答:

0赞 aazizzailani 11/12/2023 #1

您可以使用 try-except 来处理 UnicodeDecodeError,并添加信号处理以获得更好的终止。

0赞 Yegor Kozhevnikov 11/12/2023 #2

我以一种相当有趣的方式解决了这个问题,虽然不是很正确:

def monitor_input():
    global stop_flag
    try:
        while True:
            user_input = input()
            if user_input.lower() == 'stop':
                print("Stop command received. Setting stop flag.")
                stop_flag = True
                break
    except UnicodeDecodeError:
        sys.exit(1)
        sys.exit(1)

是的,从本质上讲,我只是添加了另一个 sys.exit(1)。它起作用了。它只是工作

评论

0赞 Jeremy Friesner 11/12/2023
你能解释一下它是如何工作的吗?在我看来,第二个永远不会达到,因此不会对程序的行为产生影响。我错过了什么吗?sys.exit(1)
0赞 Yegor Kozhevnikov 11/13/2023
@JeremyFriesner 这很简单:一个 sys.exit(1) 终止了等待 input() 的线程,另一个终止了整个程序。起初我还认为它不会起作用,因为程序不应该到达第二个 sys.exit(1),但它可以工作
1赞 Jeremy Friesner 11/12/2023 #3

正如您所发现的,线程和阻塞 I/O 一起可能是一个问题,因为当您想要完全关闭线程并退出程序时,将阻塞的线程从其阻塞 I/O 调用中剔除是很棘手的。

因此,让我们通过完全避免使用线程来避免这个问题;我们将 block inside ,而不是 block inside ,当用户在 stdin 上输入命令或需要递增计数器时,它将返回。input()select()

注意:这种方法在 Windows 下不起作用,因为 Windows 的实现是次优的,不允许你这样做。stdinselect()

import select
import sys
import time

i = 0
next_counter_increment_time = time.time()
while(i < 100):
  now = time.time()
  seconds_until_next_counter_increment = next_counter_increment_time-now
  if (seconds_until_next_counter_increment < 0):
     seconds_until_next_counter_increment = 0
  inReady, outReady, exRead = select.select([sys.stdin], [], [], seconds_until_next_counter_increment)
  if (sys.stdin in inReady):  # check if there is something ready for us on stdin
     try:
        user_input = input() # we know this won't block
        print("You typed:  [%s]" % user_input)
     except UnicodeDecodeError as e:
        user_input = ''
        print('Terminated by user [OK]')
        sys.exit(1)
     if user_input == 'stop':
        print("Stopping now, bye!")
        sys.exit(1)

  now = time.time()
  if (now >= next_counter_increment_time):
     i += 1
     print("Counter is now %i" % i)
     next_counter_increment_time = now + 1.0

print("Counter got to 100, exiting!")

评论

0赞 Yegor Kozhevnikov 11/13/2023
即使我使用的是 Windows,这仍然是非常有用的信息,谢谢!据我了解,它类似于 msvsrt,但对于 Linux?
0赞 Jeremy Friesner 11/13/2023
我不知道msvsrt是什么。