Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - do something until keypress or timeout

I've nearly solved this problem but I think I need a nudge in the right direction.

I want to do something every five seconds until either a certain amount of time has elapsed or the user interrupts it (in which case it finishes that iteration of the loop before finishing).

import time
import threading

def do_something():
    T0 = time.clock()
    while (time.clock() - T0) < 60 and not e.isSet(): #as long as 60s haven't elapsed
                                                      #and the flag is not set
        #here do a bunch of stuff
        time.sleep(5)

thread = threading.Thread(target=do_something, args=())
thread.start()
e = threading.Event()

while thread.isAlive():
    #here I want the main thread to wait for a keypress and, if it receives it,
    #set the event e, which will cause the thread to finish its work.

I can't work out how to make that last line work. Using raw_input() inside the loop will block until the user hits enter whether the thread finishes its work or not. Is there another module that will do what I want?

Edit: I am using Windows XP.

like image 263
Benjamin Hodgson Avatar asked Aug 01 '12 11:08

Benjamin Hodgson


People also ask

How do I make python to wait for a pressed key?

In Python 2 use raw_input(): raw_input("Press Enter to continue...") This only waits for the user to press enter though. This should wait for a keypress.

How do you press a key to exit in Python?

If you add raw_input('Press any key to exit') it will not display any error codes but it will tell you that the program exited with code 0. For example this is my first program.

Does python wait for threads to finish?

join() # Will wait for a thread until it finishes its task. You can also provide a timeout parameter in seconds (real numbers accepted) to the join() method.

What is threading timer in python?

Introduction to Python Threading Timer. The timer is a subsidiary class present in the python library named “threading”, which is generally utilized to run a code after a specified time period. Python's threading. Timer() starts after the delay specified as an argument within the threading.


3 Answers

You can use thread.interrupt_main().


Example:

import thread
import time
import threading

e = threading.Event()

def main():
    thread.start_new_thread(wait_for_input, tuple())
    thread.start_new_thread(do_something, tuple())

def wait_for_input():
    raw_input()
    e.set()

def do_something():
    T0 = time.clock()
    while (time.clock() - T0) < 60 and not e.isSet(): #as long as 60s haven't elapsed
                                                      #and the flag is not set
        #here do a bunch of stuff
        time.sleep(5)
    thread.interrupt_main() # kill the raw_input thread

try:
    thread.start_new_thread(main, tuple())
    while 1:
        time.sleep(0.1) 
except KeyboardInterrupt:
    pass 
like image 175
sloth Avatar answered Oct 03 '22 00:10

sloth


Here is how I solved the issue. I didn't really want to move over to the lower-level thread module, and I decided I was happy for the user to use CTRL-C to cause the programme to exit gracefully.

It's a bit of a bodge because of the way it repurposes KeyboardInterrupt, which means it can't really be embedded in code that does need CTRL-C to be an ungraceful exit. However it's OK for my purposes.

import time
import threading

def do_something():
    T0 = time.clock()
    while (time.clock() - T0) < 60 and not e.isSet(): #as long as 60s haven't elapsed
                                                      #and the flag is not set
        #here do a bunch of stuff
        time.sleep(5)

thread = threading.Thread(target=do_something, args=())
e = threading.Event()
thread.start()

print 'Press CTRL-C to interrupt'
while thread.isAlive():
    try: time.sleep(1) #wait 1 second, then go back and ask if thread is still alive
    except KeyboardInterrupt: #if ctrl-C is pressed within that second,
                              #catch the KeyboardInterrupt exception
        e.set() #set the flag that will kill the thread when it has finished
        print 'Exiting...'
        thread.join() #wait for the thread to finish

Update: It actually turned out to be much more straightforward to use a GUI button. The below code doesn't involve the slightly patchy repurposing of KeyboardInterrupt.

import time
import threading
import Tkinter as Tk

def do_something():
    T0 = time.clock()
    while (time.clock() - T0) < 60 and not e.isSet(): #as long as 60s haven't elapsed
                                                      #and the flag is not set
        #here do a bunch of stuff
        time.sleep(5)

def _quit():
    print 'Exiting...'
    e.set()
    thread.join() #wait for the thread to finish
    root.quit()
    root.destroy()

root = Tk.Tk()
QuitButton = Tk.Button(master=root, text='Quit', command=_quit) #the quit button
QuitButton.pack(side=Tk.BOTTOM)

thread = threading.Thread(target=do_something, args=())
e = threading.Event()
thread.start()
root.mainloop()
like image 39
Benjamin Hodgson Avatar answered Oct 02 '22 23:10

Benjamin Hodgson


NOTE: I'd written this answer before you mentioned you were using Windows XP, and as such it won't help you—select only works on sockets under Windows. I think the answer is still useful for others though, so I'll leave it here.


This gets a bit more complicated than I like to write example code for, because I'm sure it will require some debugging, but I might approach the problem this way:

I'd use select in a loop, waiting on sys.stdin, with a five-second timeout. Each time it returned, if no input was present, I'd kick off the thread again (perhaps checking to see if the last thread had actually finished running), then continue the loop. If input was present, I'd exit the loop.

When select indicates input is present, I could either just consider it an interrupt flat-out or read in the input and evaulate whether or not it constitutes a valid interruption—if not, I could buffer it pending further input to complete an interruption. If it's an interruption, I'd wait on the thread to complete the work.

like image 25
zigg Avatar answered Oct 02 '22 23:10

zigg