4

I have a question how to implement a variation of whats found here:

Set Time Limit on User Input (Scanner) Java

In my case, I would like to ignore the input if the Timelimit is reached while keeping the program alive.

String str = "";
TimerTask task = new TimerTask(){
  public void run(){
     if(str.equals("")){
         System.out.println("No Valid Input detected");
         //TimerTask should end here along with the Input
         //Other example has System.exit which will also terminate the 
         //entire program
     }else {
         // still keep the program alive. The condition itself isn't 
         //important.
     }
}

Timer timer = new Timer();
timer.schedule(task, 10*1000);
Scanner scanner = new Scanner(System.in);

do{
      System.out.println("Type a message");
      str = scanner.nextLine();
}while(!str.equals("hello"));

timer.cancel();    

REACHED HERE!

If the input is given within 10 seconds(and it's valid), the loop ends and the task is canceled, which is perfect. However, if the input is not valid and the timer ends, I would like for it to stop asking for input and skip to the "REACHED HERE" position. Is that even possible?

  • Possible duplicate of [Input using thread](https://stackoverflow.com/questions/37135654/input-using-thread) – SedJ601 Mar 30 '18 at 18:40
  • Possible duplicate of [How to stop Java Scanner from accepting input](https://stackoverflow.com/questions/28256050/how-to-stop-java-scanner-from-accepting-input) – Clint Mar 30 '18 at 19:03

1 Answers1

2

As @Sedrick mentions, the simplest solution to this is a second thread. The problem is that reading from System.in is blocking. Clearly the example you linked to solves that problem with a System.exit(), but that's too extreme for your case.

Another spin on it might be to use a Deque (double-ended queue) to relay the input, with the timeout on there:

BlockingDeque<String> deque = new LinkedBlockingDeque<>();

new Thread(() -> {
    Scanner scanner = new Scanner(System.in);
    String input;
    do {
        System.out.println("Type a message");
        input = scanner.nextLine();
        deque.add(input);
    } while (!input.equals("hello"));
}).start();

String str;
do {
    str = deque.poll(10, TimeUnit.SECONDS);
} while (str != null && !str.equals("hello"));

System.out.println("REACHED HERE!");

Expanded answer...

The idea above was to only create the thread once, and re-use the deque as the proxy for System.in. But, in the thread, the read from System.in will always be blocking - there's no clean way to interrupt the thread, short of System.exit().

This can be refined a bit though. Firstly, if the thread is marked as a daemon thread, this allows the JVM to shutdown around it still. E.g. if the main() method completes, the JVM will exit cleanly too:

Thread thread = new Thread(() -> {
    ...
});
thread.setDaemon(true);
thread.start();

However, by using InputStream.available(), it is possible to poll for waiting input. This then makes it possible to interrupt the thread cleanly:

BlockingDeque<String> deque = new LinkedBlockingDeque<>();

Thread thread = new Thread(() -> {
    Scanner scanner = new Scanner(System.in);
    String input;
    try {
        do {
            if (System.in.available() > 0) {
                input = scanner.nextLine();
                deque.add(input);
            } else
                try {
                    Thread.sleep(50);
                } catch (InterruptedException ex) {
                    System.err.println("Thread stopped");
                    break;
                }
        } while (true);
    } catch (IOException ex) {
        ex.printStackTrace();
    }
});
thread.start();

System.out.println("Type a message");
String str;
do {
    str = deque.poll(10, TimeUnit.SECONDS);
} while (str != null && !str.equals("hello"));

System.out.println("REACHED HERE!");

thread.interrupt();

It looks like there's some risk with the user typing a few letters without a line feed. At the moment that would still hang, but this didn't happen on Windows - apparently data from the command window is only released to System.in line by line.

df778899
  • 10,703
  • 1
  • 24
  • 36
  • Awesome, I didn't think to use Deque. Thank you. – Schera Haze Mar 31 '18 at 08:21
  • One more question. It seems like if the timeout occurs, the Scanner Thread keeps running. Is there an elegant way to force its shut down? – Schera Haze Mar 31 '18 at 12:50
  • The reason why I want to do that is that I am worried about the side-effects. I want to place all that Code in another while loop. So, whenever we either input the correct answer or fail to do so, it will restart the entire process. But if the Thread for Scanner is never closed, I am not sure if its gonna make a new Thread every time (which are never closed) or if its gonna reuse the current active Thread ( if there is one). – Schera Haze Mar 31 '18 at 13:39
  • Good points ... The idea was to only start the thread once, and then re-use the `deque` for multiple reads. But you're right that the thread is still not easy to release if you wanted to. Have a look at the expanded answer above for a couple of options to improve this. – df778899 Mar 31 '18 at 16:34