Подтвердить что ты не робот

Как прервать BufferedReader readLine

Я пытаюсь прочитать ввод сокета по строкам в нескольких потоках. Как я могу прервать readLine(), чтобы я мог изящно остановить поток, который он блокировал?

EDIT (bounty): можно ли это сделать без закрытия сокета?

4b9b3361

Ответ 1

Закройте гнездо на прерывистой нити. Это вызовет исключение для прерывания потока.

Для получения дополнительной информации об этой и других проблемах concurrency я настоятельно рекомендую книгу Брайана Гетца "Java concurrency на практике".

Ответ 2

Без закрытия сокета:

Сложной проблемой является не BufferedReader.readLine, а лежащая в основе read. Если поток блокируется чтением, единственный способ его получить - предоставить некоторые фактические данные или закрыть сокет (прерывание потока, вероятно, должно работать, но на практике это не так).

Таким образом, очевидное решение состоит в том, чтобы иметь два потока. Тот, который считывает необработанные данные и останется заблокированным. Второй - это поток, вызывающий readLine. Трубные данные от первой второй. Затем у вас есть доступ к блокировке, чем можно использовать для пробуждения второго потока, и примите его к соответствующему действию.

Существуют вариации. У вас может быть первый поток, использующий NIO, с одним экземпляром потока, общим для всех пользователей.

В качестве альтернативы вы можете написать readLine, который работает с NIO. Это может привести даже к относительно простой однопоточной форме, поскольку Selector.wakeup существует и работает.

Ответ 3

Я играл с этим недавно (используя Scala), и мне не понравился принятый ответ на закрытие сокета и получение исключения.

В конце концов я обнаружил, что в прерывательном потоке можно вызвать socket.shutdownInput(), чтобы выйти из вызова readLine без исключения. Я делаю этот вызов в обработчике SIGINT, чтобы я мог очистить и закрыть сокет в основном потоке.

Обратите внимание, что эквивалент существует для выходного потока с socket.shutdownOutput()

Ответ 4

Извините за то, что я старше 6 лет;-) У меня возникла необходимость в прерывистом readLine при чтении с клавиатуры, для простого приложения для консольного хобби. Другими словами, я не мог "закрыть сокет".

Как вы знаете, System.in является InputStream, который, по-видимому, уже выполняет некоторую буферизацию (вам нужно нажать Enter]). Тем не менее, кажется, что рекомендуется использовать его в BufferedReader для повышения эффективности, поэтому мой ввод:

BufferedReader consoleIn = new BufferedReader(new InputStreamReader(System.in));

Другая вещь, которую можно было бы обнаружить, заключается в том, что BufferedReader.readLine() блокируется до тех пор, пока не будет предоставлен вход (даже если поток прерывается, который, кажется, только заканчивает поток, когда readline() получает свой вход). Однако можно предсказать, когда BufferedReader.read() будет не блокировать, вызывая BufferedReader.ready() == true. (Тем не менее, == false не гарантирует блок, поэтому будьте осторожны.)

Итак, я включил приведенные выше идеи в метод, который читает символ BufferedReader по символу, проверяя между каждым символом, если поток был прерван, а также проверяет конец строки, после чего линия текста возвращается.

Вы можете найти этот код полезным, передайте переменную consoleIn, как указано выше. (Критика также может быть приветствована...):

private String interruptibleReadLine(BufferedReader reader)
        throws InterruptedException, IOException {
    Pattern line = Pattern.compile("^(.*)\\R");
    Matcher matcher;
    boolean interrupted = false;

    StringBuilder result = new StringBuilder();
    int chr = -1;
    do {
        if (reader.ready()) chr = reader.read();
        if (chr > -1) result.append((char) chr);
        matcher = line.matcher(result.toString());
        interrupted = Thread.interrupted(); // resets flag, call only once
    } while (!interrupted && !matcher.matches());
    if (interrupted) throw new InterruptedException();
    return (matcher.matches() ? matcher.group(1) : "");
}

... И в потоке, который вызывает это, поймайте исключения и соответствующим образом закончите поток.

Это было протестировано в Java 8 на Linux.

Ответ 5

вы можете создать класс Timer вокруг блока read().

вам нужно установить тайм-аут для вашего таймера.

on timeout просто прервите поток.

Ответ 6

Без закрытия сокета, без сомнения, наилучшим решением с наименьшими затратами является простое использование методов блокировки read до тех пор, пока BufferedReader не будет готов или не будет достигнут тайм-аут.

public String readLineTimeout(BufferedReader reader, long timeout) throws TimeoutException, IOException {
    long start = System.currentTimeMillis();

    while (!reader.ready()) {
        if (System.currentTimeMillis() - start >= timeout)
            throw new TimeoutException();

        // optional delay between polling
        try { Thread.sleep(50); } catch (Exception ignore) {}
    }

    return reader.readLine(); // won't block since reader is ready
}

Ответ 7

Я думаю, что вам, возможно, придется использовать что-то другое, кроме readLine(). Вы можете использовать read() и при каждой проверке итерации цикла проверить, был ли поток прерван и вышел из цикла, если он был.

BufferedReader reader = //...
int c;
while ((c = reader.read()) != -1){
  if (Thread.isInterrupted()){
    break;
  }
  if (c == '\n'){
    //newline
  }
  //...
}

Ответ 8

Эскиз для решения может быть следующим: NIO предоставляет методы для неблокирования ввода-вывода, поэтому вам нужно реализовать что-то, называемое Foo, которое использует неблокирующий NIO на конце сокета, но также предоставляет интерфейс InputStream или Reader другой конец. Если BufferedReader вводит свой собственный read, он вызывается Foo, который будет вызывать Selector.select с намерением чтения. select будет либо возвращаться, указывая на наличие большего количества данных, либо блокируется до тех пор, пока не будет доступно больше данных.

Если другой поток хочет разблокировать считыватель, он должен вызвать Selector.wakeup, и селектор может изящно вернуться, выбрасывая исключение по BufferedReader.

После этого сокет должен оставаться открытым.

Вариант A: вызовите Selector.select(timeout), чтобы сделать занятый опрос.