I have a function in a program that is implemented by a for loop that repeats "count" times. I need to be able to interrupt the loop at any time by typing 'stop' in the console. I have implemented this using two threads - one thread initializes the function with the loop:
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
and the other thread initializes a function that waits for user input via 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()
Everything works correctly, but with one exception: if the loop is not interrupted, but just waits for its completion, the function still waits for user input, and it is inconvenient to close the program, more precisely, the error appears:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
I wish I could fix this
I would like to fix this, rather for myself, since I have already found a solution using the msvcrt module, but it only works for Windows, and I would like to find a more “flexible” way to do this, since i'm just learning to program
I tried to do this with error handling:
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
This worked, but not very nicely, since the program has to be closed “twice”, that is, it does not terminate after pressing the Terminate button. As far as I understand, this problem is due to the fact that one thread is still active.
I understand that this is a very minor problem, but I would like to fix it and also learn something new, so I would be very grateful for your help!
As you discovered, threads and blocking I/O together can be a problem, since it's tricky to knock a blocked thread out of its blocking I/O call when you want to shut down the thread cleanly and exit the program.
Therefore, let's avoid that problem by avoiding the use of threads entirely; instead of blocking inside input()
, we'll block inside select()
, which will return either when the user has entered a command on stdin, or when it is time to increment the counter.
Caveat: this approach doesn't work under Windows, because Windows' implementation of stdin
is, um, sub-optimal and doesn't allow you to select()
on it.
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!")