Disclaimer: the import statements are within the functions, I know that this is uncommon. I am showing my whole program here by showing it function for function while telling my issue and my thinking. In reality, I am doing something different, I made this minimal example just for this Stackoverflow question. There are duplicate questions, but I did not find good answers in them since those only say "use multithreading" (e.g. this answer). This particular question concerns itself with how to use multithreading.
The Story: I am running a program in Python. Let's say that it is a while loop going on to infinity. It just runs happily. For example,
def job(threadname, q):
from time import sleep
c = 0
while True:
sleep(0.1) #slow the loop down
c += 1
print(c)
What I want to be able to do is that it asynchronously detects a keypress on stdin
and then interrupts execution, so that I can do whatever I want within the function it is interrupted in (or if I am running it with python3 -i program.py
, to switch over to the REPL with all my modules loaded in, remember this is a minimal example in which I do not want to highlight such concerns too much).
My idea was: I have one function that asynchronously gets the keypress, sends it via a queue to the other thread and it works. So I extended the job function as such:
def job(threadname, q):
from time import sleep
c = 0
while True:
sleep(0.1) #slow the loop down
c += 1
print(c)
ch = q.get() #extension for multithreading
handle_keypress(ch) #extension for handling keypresses
The code for handle_keypress(ch)
is:
def handle_keypress(key):
if (key == "q"):
print("Quit thread")
exit(0)
elif (key == "s"):
print("would you like to change the step size? This has not been implemented yet.")
else:
print("you pressed another key, how nice! Unfortunately, there are not anymore options available yet.")
In other words, not that interesting other than to showcase that I want to be able to do this.
At first the issue seemed to be in the job()
function. The culprit is q.get()
, which is hanging. However, it is hanging because my input thread for some reason is not asynchronous and blocks. I have no clue how to make it unblocked.
This is the function of my input thread:
def get_input(threadname, q):
#get one character, this code is adapted from https://stackoverflow.com/questions/510357/python-read-a-single-character-from-the-user
while True:
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
q.put(ch)
It is obvious to me that sys.stdin.read(1)
is blocking, but I don't know how to make it unblocked. In the state that it is now, I also cannot think of a way to handle the poison pill situation, which is why q.get()
in the job()
function is blocking.
I run the program by calling the following function:
def run_program():
from threading import Thread
from queue import Queue
queue = Queue()
thread1 = Thread( target=get_input, args=("Thread-1", queue) )
thread2 = Thread( target=job, args=("Thread-2", queue) )
thread1.start()
thread2.start()
thread1.join()
thread2.join()
My Questions: is this how you would design a program to deal with asynchronous keypresses? If so, how do I make the get_input()
function unblocked?