【问题标题】:User input with a timeout in JavaJava中超时的用户输入
【发布时间】:2020-05-14 21:41:08
【问题描述】:

我正在尝试使用此功能构建命令行界面:如果用户插入输入(在本例中为整数)花费的时间超过 15 秒,则该函数会做出默认选择 (0)。下面的代码是我到目前为止写的,它工作正常。

问题是我想添加一个新功能:如果用户输入了错误的数字(range),控制台应该打印类似("Wrong choice, you have to pick an integer between 0 - "+ range);

但是,当控制台打印消息时,计时器应该仍在运行并在 15 秒后结束此循环,以防用户不断插入错​​误的数字。如果用户最终得到一个正确的数字,它应该立即中断循环。

这是我的代码,但我对如何添加功能没有明确的想法,因为我对 Future、Callable 和 Executor 功能比较陌生。如果有人对此有更多经验,我很乐意学习!

private int getChoiceWithTimeout(int range){
       Callable<Integer> k = () -> new Scanner(System.in).nextInt();
       Long start= System.currentTimeMillis();
       int choice=0;
       ExecutorService l = Executors.newFixedThreadPool(1);  ;
       Future<Integer> g;
       System.out.println("Enter your choice in 15 seconds :");
       g= l.submit(k);
       while(System.currentTimeMillis()-start<15*1000 && !g.isDone()){
           // Wait for future
       }
       if(g.isDone()){
           try {
               choice=g.get();
           } catch (InterruptedException | ExecutionException e) {
               e.printStackTrace();
           }
       }
       g.cancel(true);
       return choice;
    }

【问题讨论】:

    标签: java timeout java.util.scanner future callable


    【解决方案1】:

    您可以使用labelled break(下面给出的代码中的done:)和boolean 变量(下面给出的代码中的valid)来跟踪输入是否有效。

    import java.util.Scanner;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class Main {
        public static void main(String[] args) {
            // Test
            System.out.println(getChoiceWithTimeout(10));
        }
    
        static int getChoiceWithTimeout(int range) {
            Callable<Integer> k = () -> new Scanner(System.in).nextInt();
            Long start = System.currentTimeMillis();
            int choice = 0;
            boolean valid;
            ExecutorService l = Executors.newFixedThreadPool(1);
            Future<Integer> g;
            System.out.println("Enter your choice in 15 seconds :");
            g = l.submit(k);
            done: while (System.currentTimeMillis() - start < 15 * 1000) {
                do {
                    valid = true;
                    if (g.isDone()) {
                        try {
                            choice = g.get();
                            if (choice >= 0 && choice <= range) {
                                break done;
                            } else {
                                throw new IllegalArgumentException();
                            }
                        } catch (InterruptedException | ExecutionException | IllegalArgumentException e) {
                            System.out.println("Wrong choice, you have to pick an integer between 0 - " + range);
                            g = l.submit(k);
                            valid = false;
                        }
                    }
                } while (!valid);
            }
    
            g.cancel(true);
            return choice;
        }
    }
    

    示例运行:不要输入任何内容,该方法将在 15 秒后返回 0,就像它当前对您的代码所做的那样

    Enter your choice in 15 seconds :
    0
    

    另一个示例运行:只要用户输入一个有效数字,该方法就会返回输入的值;否则,它将继续要求有效输入或在 15 秒后返回 0

    Enter your choice in 15 seconds :
    a
    Wrong choice, you have to pick an integer between 0 - 10
    12
    Wrong choice, you have to pick an integer between 0 - 10
    5
    5
    

    注意:使用标记为break 不是强制性的,您可以将其替换为传统的breaking 方式,但这需要您添加更多代码行。

    【讨论】:

    • 非常感谢先生!我没有考虑标记的中断,但它是解决问题的一种非常有效的方法!我看你是专家,所以我再问一个问题:假设发生了超时(所以用户没有输入任何内容,扫描仪也没有读取任何内容);当稍后在程序中调用相同的函数时,扫描仪不会读取命令行的第一个输入(因为扫描仪之前没有执行 nextInt,所以我猜它仍在等待),所以它需要 2 个输入正常工作。有没有办法解决这个问题?
    • 编辑:我在调试时注意到问题与线程池有关:显然,当发生超时时,该线程不会被中断,因此他的 Scanner 一直在等待 nextInt
    • @anonymflaco - 在没有外部同步的情况下,Scanner 对于多线程使用是不安全的。因此,Scanner 不是这项工作的正确工具。我将不得不做一些研究来写一个结论性的答案。我建议您发布一个新问题以吸引更多关注。我还建议您在发布问题之前检查stackoverflow.com/questions/9536555/…stackoverflow.com/questions/4983065/…
    • 按照您的建议,我将其更改为缓冲阅读器,现在它可以完美运行了!非常感谢您,先生,非常感谢!
    【解决方案2】:

    是的,所以您要做的是提交未来并调用 Future#get() 并使用 TimeUnit 和 Long 参数指示在放弃操作/执行之前要阻止的阈值。

    值得注意的是,不应像这样使用 ThreadPoolExecutor。它应该在方法之外定义并重新使用,然后在应用程序终止或不再需要时关闭。

        private int getChoiceWithTimeout(int range){
           Callable<Integer> callable = () -> new Scanner(System.in).nextInt();
    
           ExecutorService service = Executors.newFixedThreadPool(1);
    
           System.out.println("Enter your choice in 15 seconds :");
    
           Future<Integer> inputFuture = service.submit(callable);
    
           try {
               return inputFuture.get(15, TimeUnit.SECONDS);
           } catch (InterruptedException e) {
               throw new IllegalStateException("Thread was interrupted", e);
           } catch (ExecutionException e) {
               throw new IllegalStateException("Something went wrong", e);
           } catch (TimeoutException e) {
               // tell user they timed out or do something
               throw new IllegalStateException("Timed out! Do something here OP!");
           } finally {
               service.shutdown();
           }
        }
    

    【讨论】:

    • 感谢您的回答,但我不清楚这如何解决我的问题:在 15 秒超时的同时,如果插入错误的输入,用户应该能够继续插入输入,直到他做对或 15 秒过去。你知道这样的事情是如何实现的吗?
    【解决方案3】:

    这是一个简化的较短版本,如果用户不输入内容,它会在 n 秒后超时:

    private static final ExecutorService l = Executors.newFixedThreadPool(1);
    
    private static String getUserInputWithTimeout(int timeout) {
        Callable<String> k = () -> new Scanner(System.in).nextLine();
        LocalDateTime start = LocalDateTime.now();
        Future<String> g = l.submit(k);
        while (ChronoUnit.SECONDS.between(start, LocalDateTime.now()) < timeout) {
            if (g.isDone()) {
                try {
                    String choice = g.get();
                    return choice;
                } catch (InterruptedException | ExecutionException | IllegalArgumentException e) {
                    logger.error("ERROR", e);
                    g = l.submit(k);
                }
            }
        }
        logger.info("Timeout...");
        g.cancel(true);
        return null;
    }
    
    public static void main(String[] args) {
        logger.info("Input the data in 10 seconds or less...");
        String res = getUserInputWithTimeout(10); // 10s until timeout
        if(res != null) {
            logger.info("user response: [" + res + "]");
        }
        System.exit(0);
    }
    

    【讨论】:

    • 您是否为此方法编写了任何 Junit 测试?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-11
    • 2021-12-04
    相关资源
    最近更新 更多