如果在过去 15 秒内 System.in 上没有输入,如何使线程超时 [英] How would one time out a thread if there is no input on System.in in the last 15 seconds

查看:24
本文介绍了如果在过去 15 秒内 System.in 上没有输入,如何使线程超时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要让线程监听 system.in,连接所有输入,并且在执行某个命令或者 10 秒内没有输入时,它需要运行另一个线程来使用收集到的信息.需要明确的是,每次输入数据时,我都需要重置 10 秒.作为并发编程的初学者,我不太确定如何处理这个

I need to have thread listening to system.in, concatenating all input and, upon a certain command or if there is no input for 10 seconds it needs to run another thread that will use the collected information. To be clear, Everytime data is inputed I need to reset the 10 seconds. As a beginner to concurrent programming I'm not really sure how to approach this

推荐答案

欢迎使用 StackOverflow!

Welcome to StackOverflow!

实现您所要求的一个简单方法是 安排扫描命令并等待相应的Future 返回结果或在空闲等待一段时间时抛出异常.扫描命令我的意思是CallableSystem.in扫描下一行.

A simple approach to achieve what you are asking is to schedule a scanning command and wait for the corresponding Future to return the result or throw an exception while waiting idle for an amount of time. By scanning command I mean a Callable that will scan the next line from System.in.

在这种情况下,您将不需要使用手工制作的 Thread 来处理复杂的多线程.只需创建一个合适的 ExecutorService(通过使用来自 Executors 类)来调度命令.一个 ExecutorService 就像一个 Thread 的调度器,即一个 Thread 的池,它处理它们的生命周期并负责例如创建并启动它们.

In this case, you won't be needing to handle complex multithreading with handmade Threads. Just create a suitable ExecutorService (by using the appropriate static method call from the Executors class) to schedule commands. An ExecutorService is like a scheduler of Threads, ie a pool of Threads which handles the life span of them and is responsible for example to create them and start them.

Future 是一个接口,它的一个实例让你监控一个执行任务(比如一个Thread)的执行时间,即检查是否完成,取消它等等... Callable 是一个接口,它的实现只是在计算/方法调用后生成/返回结果,或者抛出 Exception 以防它们是无法产生结果.Future 在我们的上下文中将由 ExecutorService 的调度命令返回,让我们监控提交的 Callable 的生命周期...

Future is an interface, an instance of which lets you monitor the execution time of an execution task (such as a Thread), ie check if complete, cancel it, etc... Callable is an interface, implementations of which are just generating/returning a result after a computation/method-call, or throw an Exception in case they are unable to produce the result. Future in our context will be returned by the scheduling commands of the ExecutorService to let us monitor the life span of the submitted Callables...

我们将要提交的 Callable 将简单地返回 Scanner.nextLine 方法调用的结果.通过向调度程序提交一个 Callable,我们得到了一个 Future,它可以在给定的时间内等待 Callable 的完成.为了无限期地等待 Callable 的完成,我们使用了 get 方法.为了等待特定的超时(这是我们正在寻找的),我们使用另一个 get 方法,为它提供我们想要等待的时间.

The Callables we are going to submit will simply return the result of Scanner.nextLine method call. By submitting a Callable to the scehduler we are getting back a Future which lets as wait for completion of the Callable for a given amount of time. To wait indefinitely for the completion of the Callable we use a get method. To wait for up to a specific timeout (which is what we are looking for) we use the other get method, supplying it with the amount of time we would like to wait.

我们可以通过 Executors 在 Java 8(我正在使用,你可以从链接中看出)和更高版本中创建多种类型的调度程序(即 ExecutorServices) 辅助类(我们也可以通过实例化相应的类来创建它们,但为了简单起见,我们将使用 Executors 的静态方法).我不是这些方面的专家,但一般来说,有固定线程池,它最多允许在任何给定时间运行给定数量的Thread,有调度线程池可以以基于时间的速率和周期执行Thread,它们有单线程版本(即相同的概念,一次只有一个Thread),有缓存线程池,它根据需要创建Thread并重用现有的已完成线程,最后有是工作窃取池,它的所有线程都并行阻塞/等待工作(我不确定最后一个,但根据文档,当您的任务生成其他任务时会很有用等等上).

There are several types of schedulers (ie ExecutorServices) we can create in Java 8 (which I am using, as you can tell from the links) and above, via the Executors helper class (we can create them by instantiating the corresponding classes also, but we'll use Executors' static methods for simplicity). I am not an expert on these, but generally speaking there is the fixed thread pool which lets at most the given number of Threads to run at any given time, there is the scheduled thread pool which can execute Threads at time-based rates and periods, there are single thread versions of them (ie same concept, only one Thread at a time), there is the cached thread pool which creates Threads as needed and reuses existing finished ones, and finally there is the work stealing pool which has all its threads block/wait in parallel for work (I am not sure about the last one, but according to the docs can be useful when your tasks generate other tasks and so on).

由于我们一次提交一个 Callable(一次一个 Scanner.nextLine 调用),我们可以使用单线程版本.并且由于我们不关心周期性地执行提交的Callable,而是希望在每次完成后提交,那么我们将使用固定的单线程池 版本.

Since we are submitting one Callable at a time (one Scanner.nextLine call at a time) we can utilize the single thread versions. And since we don't care about periodically executing the submitted Callable but instead we want to submit it after every time it finishes, then we are going to use the fixed single thread pool version.

当用户的输入准备好被处理时,您也不需要启动另一个线程,但您可以使用提交 Callable 的同一线程.这是以下概念代码中的主线程:

You also don't need to have another thread started when the user's input is ready to be processed, but you can utilize the same thread that submitted the Callables. This is the main thread in the following conceptual code:

import java.util.LinkedList;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class Concept {

    public static void main(final String[] args) {
        final LinkedList<String> q = new LinkedList<>(); //The collection to hold all user's input.
        final Scanner scan = new Scanner(System.in); //The Scanner of the System.in input stream.
        final TimeUnit waitUnit = TimeUnit.SECONDS; //What unit of time should we use when waiting for input.
        final long waitAmount = 10; //How much time (in 'waitUnit' units) should we wait for.

        //An executor with a single (daemon) thread:
        final ExecutorService scheduler = Executors.newSingleThreadExecutor(r -> {
            final Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
        });

        try {
            try {
                //Main loop for reading and waiting:
                for (String input = scheduler.submit(() -> scan.nextLine()).get(waitAmount, waitUnit);
                        !Objects.equals(input, "stop");
                        input = scheduler.submit(() -> scan.nextLine()).get(waitAmount, waitUnit))
                    q.add(input); //Add the user's last input to the collection.

                //If this is reached, then the user entered "stop" as input.
                System.out.println("Ended by user's input.");
            }
            catch (final TimeoutException tx) {
                //If this is reached, then the time timed out when waiting for user's input.
                System.out.println("Ended by timeout.");
            }
            finally {
                //Here you can "consume" however you like all the user's input from the collection:
                q.forEach(line -> System.out.println(line)); //I'm just printing all of it.
            }
        }
        catch (final InterruptedException | ExecutionException x) {
            x.printStackTrace(); //This is where you handle unexpected exceptions.
        }
        finally {
            //Whatever happened, don't forget to shutdown the ExecutorService:
            scheduler.shutdown();
        }
    }
}

只要输入停止"这个词,主线程就会继续处理连接的用户输入.或者,您也可以等待 10 秒,然后将抛出 TimeoutException,再次继续处理连接的用户输入.

Just give the word "stop" as input, and the main thread will proceed with the processing of the concatenated user's input. Or, alternatively, you can wait for 10 seconds, and a TimeoutException will be thrown, again proceeding with the processing of the concatenated user's input.

我正在为 Executors' 方法调用提供 ThreadFactory.ThreadFactory 只是一个接口,它的实现为给定的 Runnables.Runnable 又是一个接口,这次它定义了一个执行计算的方法(run).在我们的例子中,这个计算是在 ExecutorService 内部创建的,用于存储我们提交的 Callable 结果的引用,以便使其可用于 返回的 Future 的 get 方法,这将使其可用于客户端的代码.我为 ExecutorService 提供了这个 ThreadFactory,它正在将每个 Thread 创建为一个 守护进程.Daemon Thread 不会阻止程序终止.当所有非守护进程线程都完成后,程序将终止,与其他(守护进程)线程是否仍在运行无关.

I am supplying the Executors' method call with a ThreadFactory. A ThreadFactory is simply an interface, implementations of which create Threads for the given Runnables. Runnable is yet again an interface, which defines this time a single method (run) which executes a computation. This computation in our case is created internally in the ExecutorService, to store a reference of the result of the Callable we submitted, so as to make it available to the get methods of the returned Future, which will in turn make it available to the client's code. This ThreadFactory, I supply the ExecutorService with, is creating each Thread to be a daemon one. Daemon Threads do not stop the program from terminating. When all non-daemon threads are done, the program is terminated, independently of whether some other (daemon) threads are still running.

因此,这归结为我在创建代码时遇到的问题:如果用户输入从超时停止而不是将停止"一词作为输入,则意味着 Callable我们提交的还没有完成.我们提交的 Callable 正在等待来自 System.in 的输入.因此该线程将无限期运行,或者直到用户输入某些内容.如果创建的 Thread 不是 daemon,则不会让程序终止.这就是为什么我要让它成为守护进程.

As such, this comes down to the problem I faced while creating the code: if the user input is stopped from timeout instead of giving the word "stop" as input, that means that the Callable we submitted does not yet have completed. The Callable we submitted is waiting for input from the System.in. So that thread will run indefinitely, or until the user enters something. If the Threads created were not daemon that would not let the program terminate. That's why I am making it daemon.

但是,如果在超时之后,您想继续从 System.in 中读取(使用(或不使用)创建的 Scanner 对象呢?然后,您必须首先维护对最后一个 ExecutorService.submit 方法调用返回的最后一个 Future 的引用.

But what if, after the timeout, you wanted to keep reading from the System.in with (or without) the Scanner object created? Then you would have to maintain a reference first to the last Future returned by the last ExecutorService.submit method call.

这就是为什么,我有另一个版本,它将扫描完全传递给另一个名为 TimedCallablewrapper 对象,您应该在每次扫描时使用它.即使在超时或以停止"字结束后,您仍应继续使用它来扫描 System.in:

So that is why, I have another version, which passes the scanning completely to another wrapper object called TimedCallable which you should use for every scanning. Even after timeout or finishing with a "stop" word, you should keep on using it to scan the System.in:

import java.util.LinkedList;
import java.util.Objects;
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;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class Main {

    public static class TimedCallable<V> implements Callable<V> {
        private final Callable<V> callable;
        private final ExecutorService scheduler;
        private Future<V> lastFuture;

        public TimedCallable(final Callable<V> callable) {
            this.callable = Objects.requireNonNull(callable);
            scheduler = Executors.newSingleThreadExecutor(r -> {
                final Thread t = new Thread(r);
                t.setDaemon(true); //Needs to be a daemon in order to let the program end.
                return t;
            });
            lastFuture = null;
        }

        @Override
        public synchronized V call() throws InterruptedException, ExecutionException {
            if (lastFuture == null)
                try {
                    return callable.call();
                }
                catch (final Exception x) {
                    throw new ExecutionException(x);
                }
            final V v = lastFuture.get();
            lastFuture = null;
            return v;
        }

        public synchronized V call(final TimeUnit timeoutUnit,
                                   final long timeoutAmount) throws TimeoutException, InterruptedException, ExecutionException {
            if (lastFuture == null)
                lastFuture = scheduler.submit(callable);
            final V v = lastFuture.get(timeoutAmount, timeoutUnit); /*If it throws TimeoutException,
            then the 'lastFuture' property will not be nulled by the following statement:*/
            lastFuture = null;
            return v;
        }
    }

    public static void main(final String[] args) {
        final LinkedList<String> q = new LinkedList<>(); //The collection to hold all user's input.
        final Scanner scan = new Scanner(System.in); //The Scanner of the System.in input stream.
        final TimeUnit waitUnit = TimeUnit.SECONDS; //What unit of time should we use when waiting for input.
        final long waitAmount = 10; //How much time (in 'waitUnit' units) should we wait for.

        //Instantiate the scanner's timed-callable:
        final TimedCallable<String> scanNextLine = new TimedCallable<>(() -> scan.nextLine());

        try {
            try {
                //Main loop for reading and waiting:
                for (String input = scanNextLine.call(waitUnit, waitAmount); !Objects.equals(input, "stop"); input = scanNextLine.call(waitUnit, waitAmount))
                    q.add(input); //Add the user's last input to the collection.

                //If this is reached, then the user entered "stop" as input.
                System.out.println("Ended by user's input.");
            }
            catch (final TimeoutException tx) {
                //If this is reached, then the time timed out when waiting for user's input.
                System.out.println("Ended by timeout.");
            }
            finally {
                //Here you can "consume" however you like all the user's input from the collection:
                q.forEach(line -> System.out.println(line)); //I'm just printing all of it.

                //Keep on using the Scanner via the TimedCallable:
                System.out.println("Enter next line:");
                System.out.println(scanNextLine.call());
                System.out.println("Enter last line:");
                System.out.println(scanNextLine.call());
            }
        }
        catch (final InterruptedException | ExecutionException x) {
            x.printStackTrace(); //This is where you handle unexpected exceptions.
        }
    }
}

最后一点:我在两个版本中都假设用户在输入句子时可能会被超时打断.例如,如果您将超时设置为 1 秒,那么用户可能没有足够的时间在超时到期并打扰他之前输入他想要的内容.为了更好地控制输入过程,您最好创建一个 GUI 并注册相应的侦听器对象.

Final note: I made the assumption in both versions that the user might be interrupted from the timeout while still entering a sentence. For example if you set the timeout to be 1 second, then the user might not have enough time to enter what he wants, before the timeout expires and disrupts him. For more control over the input process, you would be better off creating a GUI and registering corresponding listener objects.

这篇关于如果在过去 15 秒内 System.in 上没有输入,如何使线程超时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆