【问题标题】:Data loop printing in terminal killable by user input终端中的数据循环打印可由用户输入终止
【发布时间】:2019-07-24 14:54:06
【问题描述】:

我的任务是在终端打印货币汇率表,每分钟打印一次有延迟的表。在用户输入时,我将结束循环并返回海关终端菜单。如何做到这一点?

总的来说,我有一个问题要弄清楚如何获得无缝的用户体验。如果没有用户操作,我需要在终端中进行用户输入,并且仍然每分钟打印一次数据。最终,如果用户决定终止打印,如何完全终止整个数据打印循环? -> 运行标志 = ????;

这真的是正确的做法吗?

我尝试了几种方法,不幸的是,没有一种方法有效。

有人可以帮我吗?

到目前为止,我只有以下代码。循环打印汇率的自定义方法:

private void printExchangeRateTable(final Map<String, Integer> storedExchangeRates){

    boolean runFlag = true;
    do {

        printExchangeRates(storedExchangeRates);

        println("Hit any key to stop loop");

        Thread threadDelayer = new Thread(new Delayer());
        Thread threadListeningUserInput = new Thread(new UserInputListener());

        threadDelayer.start();
        threadListeningUserInput.start();

        runFlag = ????;

    } while (runFlag);
}

延迟对象

final class Delayer implements Runnable {

    public static final long MILISECONDS = 1000L;

    @Override
    public void run(){
        try {
            Thread.sleep(60 * MILISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

用户输入监听器

class UserInputListener implements Runnable {

    @Override
    public void run()  {
        Scanner scanner = new Scanner(System.in);
        val userInput = scanner.nextLine();
        if(userInput != null) {
            Thread.currentThread().interrupt();
        }
    }
}

【问题讨论】:

    标签: java concurrency terminal


    【解决方案1】:

    您可以使用ScheduledExecutorService 设置重复任务,但在这里,将用户输入的读数发送到后台任务会更容易

    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        CompletableFuture<String> userInput = CompletableFuture.supplyAsync(s::nextLine);
        while(!userInput.isDone()) {
            printData();
            try {
                userInput.get(1, TimeUnit.MINUTES);
            }
            catch(ExecutionException|InterruptedException ex) {} // handled after the loop
            catch(TimeoutException ex) {
                continue;
            }
        }
        String input = userInput.join();
        // handle input
    }
    
    private static void printData() {
        System.out.println("data");
    }
    

    CompletableFuture.supplyAsync(s::nextLine) 会将s.nextLine() 的执行提交给后台任务。然后,循环将一直运行到后台任务完成,每分钟执行一次printData()。这是通过userInput.get(1, TimeUnit.MINUTES) 实现的,如果在指定时间后没有可用结果,它将抛出TimeoutExceptioncontinue 在这里不是必需的,因为无论如何循环都会继续,但我明确表示了。

    其他可能的异常是在本质上处理的。中断不应该发生,但是通过进入下一次迭代,它可以用作甚至在时间之前刷新打印数据的信号。当ExecutionException发生时,无论如何在循环后的join()语句中获取实际用户输入时,都会报错状态。

    这个循环也可以合并到一个更大的循环中

    Scanner s = new Scanner(System.in);
    for(boolean quit = false; !quit; ) {
        CompletableFuture<String> userInput = CompletableFuture.supplyAsync(s::nextLine);
        while(!userInput.isDone()) {
            printData();
            try {
                userInput.get(1, TimeUnit.MINUTES);
            }
            catch(ExecutionException|InterruptedException ex) {} // handled implicitly
            catch(TimeoutException ex) {
                continue;
            }
        }
        String input = userInput.join();
        // handle input
        if(input.equals("exit")) quit = true;
    }
    

    【讨论】:

      【解决方案2】:

      你可以试试这个。我正在使用与您相同的逻辑来读取用户输入,但对于打印,我将使用一个计时器(这里计划每 1 秒运行一次,但您可以更改它)。管理计时器更容易,并且可以从另一个线程中取消。

      public class Main {
      public static void main(String[] args) {
          Thread inputListener = new Thread(new UserInputListener());
          inputListener.start();
      }
      
      static class MyTimerTask extends TimerTask
      {
          public void run()
          {
              System.out.println("some output");
          }
      }
      
      static class UserInputListener implements Runnable {
          @Override
          public void run()  {
              Timer timer = new Timer();
              TimerTask task = new MyTimerTask();
              timer.schedule(task, 0, 1000);
              Scanner scanner = new Scanner(System.in);
              scanner.nextLine();
              timer.cancel();
          }
        }
      }
      

      【讨论】:

      【解决方案3】:

      首先,您需要两个线程,但您不必创建两个 - 您可以使用主线程进行用户输入,并在程序开始时启动另一个线程,该线程将每分钟打印一次汇率。

      每个线程内部都会有一个无限(或标志控制)循环。但是,与其自己管理,我建议您使用ScheduledExecutorService,否则您将很难中断线程。

      这是一个例子:

      import java.util.Scanner;
      import java.util.concurrent.Executors;
      import java.util.concurrent.ScheduledExecutorService;
      import java.util.concurrent.TimeUnit;
      
      public class Example {
      
          public static void main(String[] args) {
              ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
              executor.scheduleWithFixedDelay(Example::printData, 1, 1, TimeUnit.MINUTES);
      
              Scanner scanner = new Scanner(System.in);
      
              while (true) {
                  String userInput = scanner.nextLine();
      
                  if (userInput.equals("exit")) {
                      break;
                  }
              }
      
              executor.shutdown();
          }
      
          private static void printData() {
              System.out.println("data");
          }
      
      }
      

      如果没有其他用户输入需要处理,并且仅用于停止后台线程,则根本不需要该线程中的循环,您可以将其简化为:

      public static void main(String[] args) {
          ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
          executor.scheduleWithFixedDelay(Example::printData, 1, 1, TimeUnit.MINUTES);
      
          Scanner scanner = new Scanner(System.in);
          scanner.nextLine();
          executor.shutdown();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-05
        • 2020-02-24
        • 1970-01-01
        • 2014-04-18
        相关资源
        最近更新 更多