0

Sorry I'm new to programming, and don't really understand how this Thread thing works. My goal was for this input to be timed, and I found some code that does that. However, I'm confused about the structure of this Thread because if you are "too slow", the program never continues on to print "checkpoint" as desired. It just sort of... freezes... Why is it getting stuck?

import time
from threading import Thread

answer = None

def check():
    # waits for user input for 3 seconds
    for i in range(3):
        time.sleep(1)
        if answer != None:
            return
    print('too slow')

Thread(target = check).start()

answer = input("Input something: ")

print('checkpoint')

One thing I tried is:

t = Thread(target = check)
t.start()
answer = input("Input something: ")
# also tried t.join()
if t.is_alive:
    print('hi')

I tried to solve this program by trying to raise and catch an exception. However, I couldn't catch the exception. How do I catch it? (Or is there another solution to the problem I am having?)

import time
from threading import Thread

answer = None

def check():
    # waits for user input for 3 seconds
    for i in range(3):
        time.sleep(1)
        if answer != None:
            return
    print('too slow')
    # was hoping to catch this as an exception
    raise TimeoutError

# starts new thread
Thread(target = check).start()

# prompts user for an input
answer = input("Input something: ")

print('checkpoint')

What's good: When you type something into the input prompt within 3 seconds, it prints "checkpoint" and continues on with code.

What's bad: If you take "too long", the program prints "too slow!" as expected, BUT then it stops executing code and just sort of... freezes. So to try to fix this, I was hoping to raise a Timeout Error and then catch it, but I don't know how to catch it. This didn't catch the error:

try:
    Thread(target = check).start()
except:
    pass

This didn't either:

try:
    answer = input("Input something: ")
except:
    pass

Could I get some help? Thank you!

Edit: Forgot to mention that I am using linux so a lot of the solutions for my application did not work for me like msvcrt or keyboard. And modules that do work for Linux seem not to be "non-blocking."

Ev C
  • 67
  • 1
  • 2
  • 13
  • better find on internet function `keypress()` or `getchar()` (or `getch()`)` and use it instead of `input()`. You can't kill `input()`. It will wait till you press enter. Thread is like separated program - if you raise error it thread then you can catch it in thread, but not in main code. – furas Jul 04 '19 at 01:24
  • What module is keypress() from? Can you interrupt getchar() or getch()? I didn't see a way to do that (or download it [I forgot to mention I am on Linux, and maybe that's the reason I couldn't install getch]). – Ev C Jul 05 '19 at 21:37
  • as I know there is no module with these functions. You can find it in some tutorials. Use words "python keypress getchar getch" in Google. You don't have to interrupt getchar/getch because they don't wait for keys. They return empty string if there is no chars. You have to run it in loop to check it all time. In the same loop you can do other things - you can check time and exit this loop if you user doesn't press any key on time. On Linux it uses standard module `select` to check if there is char in buffer and then you can read it – furas Jul 05 '19 at 21:45
  • [Python read a single character from the user](https://stackoverflow.com/questions/510357/python-read-a-single-character-from-the-user) – furas Jul 05 '19 at 21:48
  • this example has `kbhit()` and it read char only when it is pressed [how to implement kbhit() on Linux (Python recipe)](http://code.activestate.com/recipes/572182-how-to-implement-kbhit-on-linux/) – furas Jul 05 '19 at 21:57
  • Looks like you're trying to implement what I have [here](https://stackoverflow.com/a/53180738/9059420). – Darkonaut Jul 05 '19 at 22:28
  • Thank you everyone! I wish I'd looked at these before I found a solution. Once I figured out what I was looking for was "non-blocking," finding a solution became so much easier. – Ev C Jul 05 '19 at 22:50

2 Answers2

0

You should think of the two threads as two separate programs, but sharing the same variables.

Thread 1 consists of everything that isn't indented in your code. It launches a thread, then it waits for user input, then it prints "checkpoint". Then it's done.

Thread 2 consists of the function check. It checks to see if the variable isn't None. If that happens it's done. If that doesn't happen in three seconds, it prints "too slow" and now it's done.

Neither thread "knows" what the other one is doing, except they share one variable, answer.

The program as a whole will exit when all its threads are finished.

That's it. That's what you've written. So if you type something, the program exits because Thread 1 will always exit after you type something. Thread 2 exits once it sees the variable isn't None.

If you don't type anything, Thread 1 will just sit there and wait for you, forever. That's how the input function works. Thread 2 will exit after 3 seconds or less, but that doesn't affect Thread 1.

You can't throw an Exception from one Thread to another. So you can't throw an exception from Thread 2 and have Thread 1 handle it.

Have you tried typing something AFTER the message "too slow" appears? When you do, Thread 1 (and therefore your program) will exit.

The bottom line is that you can't use the input function in cases like this, because that function blocks the flow of its thread until the user types something. There is nothing any other thread can do to make it continue.

Paul Cornelius
  • 9,245
  • 1
  • 15
  • 24
  • I guess you are right. Thank you for guiding me. There is no way to stop the thread. I tried to solve the problem by removing "input" and using keyboard.is_pressed('space') instead, but despite pressing 'space' it always returned False though the documentation said pressed key would return True. I believe this is because I am on Linux and a lot of these keyboard listeners which don't require a keyboard input to continue only work on MacOS or Windows, sadly... so I gave up. I'm not trying to solve this problem anymore :( – Ev C Jul 05 '19 at 21:19
0

DISCLAIMER: THIS DOESN'T ANSWER THE QUESTION BUT IN CASE YOU WANT TO KNOW HOW I GOT AROUND THE "INPUT" THING, HERE IS MY SOLUTION TO THE PROBLEM.

Actually I found something that works! It's a little strange but it works for what I am trying to do, thanks to @rayryeng 's answer here: detect key press in python?.

Problem Statement: Continue the program when 'enter' is pressed, and timeout if input takes too long. This does exactly that, although it displays strangely to the console... PS. I had to run this in my terminal as 'sudo' or it wouldn't work in my scratch file for whatever reason.

import curses
import os
from time import time, sleep

def main(win):
    win.nodelay(True) # True turns on "non-blocking"
    key=""
    win.clear()
    win.addstr("Please press 'Enter' to continue:")
    start_time = time()
    while 1:
        end_time = time()
        try:
            if end_time-start_time > 5: # 5 seconds
                return 'You are too slow!'

            else:
                key = win.getkey()
                if key == os.linesep:
                    return 'OK. Continuing on...'

        except Exception as e:
            # No input
            pass

p = curses.wrapper(main)
print(p) #-> either 'You are too slow!' or 'OK. Continuing on...'

I guess if you actually wanted to store the input you can modify it to be something like this:

def main(win):
    win.nodelay(True) # True turns on "non-blocking"
    key=""
    win.clear()
    win.addstr("Please press 'Enter' to continue:")
    start_time = time()
    while 1:
        end_time = time()
        try:
            if end_time-start_time > 5: # 5 seconds
                return 'You are too slow!'

            else:
                key = win.getkey() # gets a single char
                if key: # == os.linesep:
                    return str(key) # returns that single char

        except Exception as e:
            # No input
            pass

p = curses.wrapper(main)
print(p) #-> either 'You are too slow!' or character entered

And if you want to store more characters you could do something like this (just be aware that it also stores the "enter" key in the resulting string):

import curses import os from time import time, sleep

def main(win):
    win.nodelay(True) # True turns on "non-blocking"
    key=""
    win.clear()
    win.addstr("Please press 'Enter' to continue:")
    start_time = time()
    result = key # empty string
    while 1:
        end_time = time()
        try:
            if end_time-start_time > 5: # 5 seconds
                return 'You are too slow!'

            else:
                key = win.getkey() # gets single char
                result = result + str(key) # adds characters to the empty string
                if key == os.linesep: # "new line"
                    return result

        except Exception as e:
            # No input
            pass

p = curses.wrapper(main)
print(p) #-> either 'You are too slow!' or characters entered
Ev C
  • 67
  • 1
  • 2
  • 13