Как прервать чтение на 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
}       
Другие вопросы по тегам