如何中断scanner.nextLine()方法的调用

3
在SO上有很多关于中断读取system.in的线程的帖子,但我在这里寻求的是如何最好地编写我想要实现的代码的建议。 我有一个 getlogin() 方法需要执行以下操作:要求用户输入所需的登录环境详细信息,如果6秒后用户没有输入有效值("live"或"test"),则将 userlogin 变量设置为 "test" 并返回给调用者。 对于 getlogin() 实现,我采取了以下方法: 1.启动两个线程,它们分别执行以下操作: - thread1 创建扫描器对象,然后调用 scanner.nextline() 并根据用户输入设置变量 userlogin。在退出 thread1 之前中断 thread2。 - thread2 等待6秒,如果在此后仍未设置 userlogin,则为 userlogin 设置默认值。在退出 thread2 之前中断 thread1。 2.加入 thread2,以防止主线程将 userlogin 返回为空 3.返回 userlogin
我对这种方法的问题在于,scanner.nextline() 在 thread2 调用 thread1.interrupt 时不会中断,这就是为什么我在步骤2中没有加入 thread1 的原因,因为主线程会挂起。是否有一种方法让 thread1 在 thread2 中断后完成?还是说这种方法完全过度设计,有更简单的方法来实现这个契约?

@Mathkute的答案使用available是最简单的。一个轮询。虽然它本身不会中断,但它确保在有数据时发生阻塞。然而,没有必要像被接受的答案那样使用awt。如果您通过类似于我的搜索找到了这个问题,请向下滚动查看Mathkute的答案。 - LAFK says Reinstate Monica
4个回答

4
最简单的解决方案是在“读取”线程中公开底层流,并从超时线程关闭该流。这应该会打断读取并引发异常。处理此异常,您应该能够继续进行逻辑。唯一需要注意的是,您将无法再次重用相同的流。不幸的是,没有简单的方法来处理阻塞系统调用的中断。
编辑:按照完全不同的推理路线; 鉴于我们不能仅仅为了中断而关闭输入流,我所能想到的唯一方法是使用Robot类提供的“编程用户输入”功能。以下是适合我的示例:
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();
        }
    }

}

上述代码使用的逻辑是一旦超时时间到期,我们模拟一个换行符,这将导致“name”变量为空。然后我们进行检查,执行必要的逻辑并设置适当的用户名。
上述方法的陷阱是:
- 使用 AWT 的 Robot 类,可能与无头终端不兼容。 - 假设焦点窗口是控制台窗口。如果焦点在其他地方,则 ENTER 键按下将在该窗口中注册,而不是在您的应用程序窗口中。
希望这可以帮助您。我现在真的没有更多的想法了。 :)

谢谢您的建议。目前我的输入流是system.in,如果我关闭它,就会出现问题。有没有一种方法在扫描器内部引发异常,模拟inputstream.in.close事件? - jule64
@jule64:使用新方法更新了我的帖子。 - Sanjay T. Sharma
非常感谢您进一步研究此事。请给我一些时间来看看它是否适用于我的问题,但由于您的工作,我已经感觉到自己离一个可行的解决方案更近了。+1 - jule64
我正在探索@bmargulies建议的解决方案,该解决方案涉及commons-io的CloseShieldInputStream,以回应此线程https://dev59.com/XV3Va4cB1Zd3GeqPAXH4上的类似问题。我将尝试在今天返回结果。 - jule64
抱歉回复晚了。最终我采用了你的解决方案。非常满意结果。再次感谢! - jule64
@jule64:很高兴能帮到你。祝你的项目好运! :) - Sanjay T. Sharma

3
为什么不直接使用System.in.available()来轮询是否有可读字节?这是非阻塞的方式:当确定它可以工作且不会阻塞时,可以调用Scanner.nextLine()(它是阻塞的)。

0

可以使用FutureTask和lambda表达式:

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
}

-1

geri的另一个版本答案可能是:

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 ...;
}

还有一种好的解决方案,我发布了一个JUnit测试,也许对于研究和实验是有用的,请查看 https://pastebin.com/nfgkLQda - geri
这会使读取操作在自己的线程中挂起,防止JVM终止。 - Jim Garrison

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接