如果在过去 15 秒内 System.in 上没有输入,如何使线程超时 [英] How would one time out a thread if there is no input on System.in in the last 15 seconds
问题描述
我需要让线程监听 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
返回结果或在空闲等待一段时间时抛出异常.扫描命令我的意思是Callable
将 从System.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 Thread
s. 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 Thread
s, ie a pool of Thread
s 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 Callable
s...
我们将要提交的 Callable
将简单地返回 Scanner.nextLine
方法调用的结果.通过向调度程序提交一个 Callable
,我们得到了一个 Future
,它可以在给定的时间内等待 Callable
的完成.为了无限期地等待 Callable
的完成,我们使用了 get
方法.为了等待特定的超时(这是我们正在寻找的),我们使用另一个 get
方法,为它提供我们想要等待的时间.
The Callable
s 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(我正在使用,你可以从链接中看出)和更高版本中创建多种类型的调度程序(即
辅助类(我们也可以通过实例化相应的类来创建它们,但为了简单起见,我们将使用 ExecutorService
s)Executors
的静态方法).我不是这些方面的专家,但一般来说,有固定线程池,它最多允许在任何给定时间运行给定数量的Thread
,有调度线程池可以以基于时间的速率和周期执行Thread
,它们有单线程版本(即相同的概念,一次只有一个Thread
),有缓存线程池,它根据需要创建Thread
并重用现有的已完成线程,最后有是工作窃取池,它的所有线程都并行阻塞/等待工作(我不确定最后一个,但根据文档,当您的任务生成其他任务时会很有用等等上).
There are several types of schedulers (ie ExecutorService
s) 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 Thread
s to run at any given time, there is the scheduled thread pool which can execute Thread
s 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 Thread
s 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 Callable
s. 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
只是一个接口,它的实现为给定的 Runnable
s.Runnable
又是一个接口,这次它定义了一个执行计算的方法(run
).在我们的例子中,这个计算是在 ExecutorService
内部创建的,用于存储我们提交的 Callable
结果的引用,以便使其可用于 返回的
方法,这将使其可用于客户端的代码.我为 Future
的 getExecutorService
提供了这个 ThreadFactory
,它正在将每个 Thread
创建为一个 守护进程.Daemon Thread
不会阻止程序终止.当所有非守护进程线程都完成后,程序将终止,与其他(守护进程)线程是否仍在运行无关.
I am supplying the Executors
' method call with a ThreadFactory
. A ThreadFactory
is simply an interface, implementations of which create Thread
s for the given Runnable
s. 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 Thread
s 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 Thread
s 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.
这就是为什么,我有另一个版本,它将扫描完全传递给另一个名为 TimedCallable
的 wrapper 对象,您应该在每次扫描时使用它.即使在超时或以停止"字结束后,您仍应继续使用它来扫描 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屋!