提问人:Yegor Kozhevnikov 提问时间:11/12/2023 更新时间:11/12/2023 访问量:56
使用带有多线程的 input() 优雅的循环退出
Elegant loop exit using input() with multithreading
问:
我在一个程序中有一个函数,它是由重复“计数”次数的 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
这奏效了,但不是很好,因为程序必须“两次”关闭,也就是说,按下终止按钮后它不会终止。据我了解,这个问题是由于一个线程仍然处于活动状态。
我知道这是一个非常小的问题,但我想解决它并学习一些新东西,所以我非常感谢您的帮助!
答:
您可以使用 try-except 来处理 UnicodeDecodeError,并添加信号处理以获得更好的终止。
我以一种相当有趣的方式解决了这个问题,虽然不是很正确:
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)。它起作用了。它只是工作
评论
sys.exit(1)
正如您所发现的,线程和阻塞 I/O 一起可能是一个问题,因为当您想要完全关闭线程并退出程序时,将阻塞的线程从其阻塞 I/O 调用中剔除是很棘手的。
因此,让我们通过完全避免使用线程来避免这个问题;我们将 block inside ,而不是 block inside ,当用户在 stdin 上输入命令或需要递增计数器时,它将返回。input()
select()
注意:这种方法在 Windows 下不起作用,因为 Windows 的实现是次优的,不允许你这样做。stdin
select()
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!")
评论