I have a Java thread:
class MyThread extends Thread {
@Override
开发者_JS百科public void run() {
BufferedReader stdin =
new BufferedReader(new InputStreamReader(System.in));
String msg;
try {
while ((msg = stdin.readLine()) != null) {
System.out.println("Got: " + msg);
}
System.out.println("Aborted.");
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
In another thread, how do I abort the stdin.readline()
call in this thread, so that this thread prints Aborted.
? I have tried System.in.close()
, but that doesn't make any difference, stdin.readline()
is still blocking.
I'm interested in solutions without
- busy waiting (because that burns 100% CPU);
- sleeping (because then the program doesn't respond instantly to
System.in
).
Heinz Kabutz's newsletter shows how to abort System.in
reads:
import java.io.*;
import java.util.concurrent.*;
class ConsoleInputReadTask implements Callable<String> {
public String call() throws IOException {
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));
System.out.println("ConsoleInputReadTask run() called.");
String input;
do {
System.out.println("Please type something: ");
try {
// wait until we have data to complete a readLine()
while (!br.ready()) {
Thread.sleep(200);
}
input = br.readLine();
} catch (InterruptedException e) {
System.out.println("ConsoleInputReadTask() cancelled");
return null;
}
} while ("".equals(input));
System.out.println("Thank You for providing input!");
return input;
}
}
public class ConsoleInput {
private final int tries;
private final int timeout;
private final TimeUnit unit;
public ConsoleInput(int tries, int timeout, TimeUnit unit) {
this.tries = tries;
this.timeout = timeout;
this.unit = unit;
}
public String readLine() throws InterruptedException {
ExecutorService ex = Executors.newSingleThreadExecutor();
String input = null;
try {
// start working
for (int i = 0; i < tries; i++) {
System.out.println(String.valueOf(i + 1) + ". loop");
Future<String> result = ex.submit(
new ConsoleInputReadTask());
try {
input = result.get(timeout, unit);
break;
} catch (ExecutionException e) {
e.getCause().printStackTrace();
} catch (TimeoutException e) {
System.out.println("Cancelling reading task");
result.cancel(true);
System.out.println("\nThread cancelled. input is null");
}
}
} finally {
ex.shutdownNow();
}
return input;
}
}
Now, I don't know whether this approach leaks, isn't portable or has any non-obvious side-effects. Personally, I would be reluctant to use it.
You might be able to do something with NIO channels and file descriptors - my own experiments with them didn't yield any results.
How about...
private static BufferedReader stdInCh = new BufferedReader(
new InputStreamReader(Channels.newInputStream((
new FileInputStream(FileDescriptor.in)).getChannel())));
A thread where stdInch.readline()
is called is now interruptible and the readline() will throw a java.nio.channels.ClosedByInterruptException
.
My first reaction is that a thread and System.in
really don't go together.
So first, split this so that the thread code does not touch any static including System.in
.
A thread reads from InputStream
and passes into a buffer. Pass an InputStream
into your existing thread that reads from the buffer but also checks that you haven't aborted.
InputStream
can never throw InterruptedException
because it's not in its contract. (You're supposed to keep checking available
before reading.) In fact, that exception is not in the contract of most classes and methods! (And for the most part they don't bother calling available
.)
A bigger problem is that InterruptibleChannel
(which does have it in the contract) doesn't guarantee success either. Some Channels lie about being interruptible, while classes that use the channels may block anyway. For example, you might think Channels.newChannel(System.in)
will get you a nice InterruptibleChannel
. But it's a lie that is attested to by the source code comments "Not really interruptible" and "Block at most once" (OpenJDK 16). (Yes, it really does block, I've checked. Ridiculous!)
A combination that I've found to work is to use new FileInputStream(FileDescriptor.in).getChannel()
with Scanner
.
Scanner scanner = new Scanner(new FileInputStream(FileDescriptor.in).getChannel())
while (!scanner.hasNextLine())
Thread.sleep(100); // Internally checks Thread.interrupted() and throws InterruptedException
String line = scanner.nextLine()
This really shouldn't be a problem. It's straightforward to write a class that checks System.in.available()
, Thread.interrupted()
, throws InterruptedException
, etc.
精彩评论