how to interrupt a scanner.nextline() call

[亡魂溺海] 提交于 2019-12-02 00:31:07

The simplest solution is to expose the underlying stream in the "reading" thread and close that stream from the timeout thread. This should interrupt the reading and raise an exception. Handle this exception and you should be able to proceed with your logic. The only gotcha is that you won't be able to re-use the same stream again. Unfortunately there is no easy way to deal with interruption of blocking system calls.

EDIT:

Following a completely different line of reasoning; given that we can't close the input stream just to interrupt it, the only way I can think of is to use the "programmatic user input" facilities offered by the Robot class. Here is an example which works out for me:

import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

public class ConsoleTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        new TimeoutThread().start();
        new ReaderThread().start();
    }

}

class ReaderThread extends Thread {

    @Override
    public void run() {
        System.out.print("Please enter your name: ");
        try(Scanner in = new Scanner(System.in)) {
            String name = in.nextLine();
            if(name.trim().isEmpty()) {
                name = "TEST"; // default user name
            }
            System.out.println("Name entered = " + name);
        }
    }

}

class TimeoutThread extends Thread {

    @Override
    public void run() {
        try {
            Thread.sleep(TimeUnit.SECONDS.toMillis(5));
            Robot robot = new Robot();
            robot.keyPress(KeyEvent.VK_ENTER);
            robot.keyRelease(KeyEvent.VK_ENTER);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

}

The above code uses the logic that once that timeout has expired, we simulate a newline which will cause the "name" variable to be blank. Then we have a check which performs the necessary logic and sets the appropriate user name.

The gotcha about the above approach is that it:

  • Uses Robot class of AWT so might not play well with headless terminals (?)
  • Assumes that the focus window is the console window. If the focus is somewhere else, the ENTER key-press will be registered for that window as opposed to your application window.

Hope this helps you out. I'm really out of ideas now. :)

A FutureTask together with a lambda expression can also be used:

FutureTask<String> readNextLine = new FutureTask<String>(() -> {
  return scanner.nextLine();
});

ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(readNextLine);

try {
  String token = readNextLine.get(5000, TimeUnit.MILLISECONDS);
  ...
} catch (TimeoutException e) {
  // handle time out
}

Why not just poll with System.in.available() if there are bytes to read? It is non-blocking: one can do the call to Scanner.nextLine(), which is blocking, when sure it works and does not block.

Another version of geri's answer would be:

ExecutorService executor = Executors.newFixedThreadPool(1);

Future<String> future = executor.submit(() -> {
    try (Scanner in = new Scanner(System.in)) {
        return in.nextLine();
    }
});

try {
    return future.get(5, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e1) {
    return ...;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!