Как прервать чтение на System.in?
Если я начну читать с System.in
, он будет блокировать поток, пока не получит данные. Нет способа остановить это. Вот все способы, которые я пробовал:
- Прерывание потока
- Остановка потока
- закрытие
System.in
- призвание
System.exit(0)
действительно останавливает поток, но это также убивает мое приложение, поэтому не идеально. - Ввод char в консоль приводит к возврату метода, но я не могу полагаться на пользовательский ввод.
Пример кода, который не работает:
public static void main(String[] args) throws InterruptedException {
Thread th = new Thread(() -> {
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
});
th.start();
Thread.sleep(1000);
System.in.close();
Thread.sleep(1000);
th.interrupt();
Thread.sleep(1000);
th.stop();
Thread.sleep(1000);
System.out.println(th.isAlive()); // Outputs true
}
Когда я запускаю этот код, он выведет true
и бежать вечно.
Как читать из System.in
прерываемым образом?
2 ответа
Я написал класс-обёртку InputStream, которая позволяет прерываться:
package de.piegames.voicepi.stt;
import java.io.IOException;
import java.io.InputStream;
public class InterruptibleInputStream extends InputStream {
protected final InputStream in;
public InterruptibleInputStream(InputStream in) {
this.in = in;
}
/**
* This will read one byte, blocking if needed. If the thread is interrupted while reading, it will stop and throw
* an {@link IOException}.
*/
@Override
public int read() throws IOException {
while (!Thread.interrupted())
if (in.available() > 0)
return in.read();
else
Thread.yield();
throw new IOException("Thread interrupted while reading");
}
/**
* This will read multiple bytes into a buffer. While reading the first byte it will block and wait in an
* interruptable way until one is available. For the remaining bytes, it will stop reading when none are available
* anymore. If the thread is interrupted, it will return -1.
*/
@Override
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = -1;
while (!Thread.interrupted())
if (in.available() > 0) {
c = in.read();
break;
} else
Thread.yield();
if (c == -1) {
return -1;
}
b[off] = (byte) c;
int i = 1;
try {
for (; i < len; i++) {
c = -1;
if (in.available() > 0)
c = in.read();
if (c == -1) {
break;
}
b[off + i] = (byte) c;
}
} catch (IOException ee) {
}
return i;
}
@Override
public int available() throws IOException {
return in.available();
}
@Override
public void close() throws IOException {
in.close();
}
@Override
public synchronized void mark(int readlimit) {
in.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
in.reset();
}
@Override
public boolean markSupported() {
return in.markSupported();
}
}
Настроить Thread.yield()
спать до тех пор, пока вы можете принять максимальную задержку, и подготовиться к некоторым исключениям при прерывании, но помимо этого он должен работать нормально.
Вы должны спроектировать метод run, чтобы он сам мог определить, когда завершить работу. Вызов stop() или подобных методов в потоке был бы небезопасен.
Однако все еще остается вопрос о том, как избежать блокировки внутри System.in.read? Для этого вы можете опрашивать System.in.available, пока он не вернет> 0 до чтения.
Пример кода:
Thread th = new Thread(() -> {
try {
while(System.in.available() < 1) {
Thread.sleep(200);
}
System.in.read();
} catch (InterruptedException e) {
// sleep interrupted
} catch (IOException e) {
e.printStackTrace();
}
});
Конечно, обычно считается предпочтительным использовать блокирующий метод ввода-вывода, а не опрос. Но опрос имеет свое применение; в вашей ситуации это позволяет этому потоку выходить чисто.
Лучший подход:
Лучшим подходом, позволяющим избежать опроса, было бы реструктурирование кода таким образом, чтобы любому потоку, который вы намереваетесь уничтожить, не был предоставлен прямой доступ к System.in
, Это потому, что System.in является InputStream, который не должен быть закрыт. Вместо этого основной поток или другой выделенный поток будет читать из System.in (блокировка), а затем записывать любое содержимое в буфер. Этот буфер, в свою очередь, будет контролироваться потоком, который вы собираетесь уничтожить.
Пример кода:
public static void main(String[] args) throws InterruptedException, IOException {
PipedOutputStream stagingPipe = new PipedOutputStream();
PipedInputStream releasingPipe = new PipedInputStream(stagingPipe);
Thread stagingThread = new Thread(() -> {
try {
while(true) {
stagingPipe.write(System.in.read());
}
} catch (IOException e) {
e.printStackTrace();
}
});
stagingThread.setDaemon(true);
stagingThread.start();
Thread th = new Thread(() -> {
try {
releasingPipe.read();
} catch (InterruptedIOException e) {
// read interrupted
} catch (IOException e) {
e.printStackTrace();
}
});
th.start();
Thread.sleep(1000);
Thread.sleep(1000);
th.interrupt();
Thread.sleep(1000);
Thread.sleep(1000);
System.out.println(th.isAlive()); // Outputs false
}