Вопрос-ответ

Is it possible to read from a InputStream with a timeout?

Возможно ли чтение из InputStream с таймаутом?

В частности, проблема заключается в написании метода, подобного этому:

int maybeRead(InputStream in, long timeout)

где возвращаемое значение такое же, как в.read(), если данные доступны в течение миллисекунд 'timeout', и -2 в противном случае. Прежде чем метод вернется, все созданные потоки должны завершиться.

Чтобы избежать споров, тема здесь java.io.InputStream, как задокументировано Sun (любая версия Java). Пожалуйста, обратите внимание, что это не так просто, как кажется. Ниже приведены некоторые факты, которые подтверждаются непосредственно документацией Sun.


  1. Метод in.read() может быть непрерываемым.


  2. Перенос InputStream в Reader или InterruptibleChannel не помогает, потому что все, что эти классы могут делать, это вызывать методы InputStream. Если бы было возможно использовать эти классы, можно было бы написать решение, которое просто выполняет ту же логику непосредственно во InputStream.


  3. Для in.available() всегда допустимо возвращать 0.


  4. Метод in.close() может блокировать или ничего не делать.


  5. Общего способа прервать другой поток не существует.


Переведено автоматически
Ответ 1

Использование InputStream.available()


Для System.in.available() всегда допустимо возвращать 0.


Я обнаружил обратное - он всегда возвращает наилучшее значение для количества доступных байтов. Javadoc для InputStream.available():

Returns an estimate of the number of bytes that can be read (or skipped over) 
from this input stream without blocking by the next invocation of a method for
this input stream.

Оценка неизбежна из-за сроков / устаревания. Цифра может быть одноразовой, поскольку постоянно поступают новые данные. Однако он всегда "догоняет" при следующем вызове - он должен учитывать все поступившие данные, за исключением тех, которые поступают только в момент нового вызова. Постоянный возврат 0 при наличии данных не соответствует приведенному выше условию.

Первое предостережение: конкретные подклассы InputStream отвечают за available()

InputStream это абстрактный класс. У него нет источника данных. Для него бессмысленно иметь доступные данные. Следовательно, javadoc для available() также указывает:

The available method for class InputStream always returns 0.

This method should be overridden by subclasses.

И действительно, конкретные классы входного потока переопределяют available() , предоставляя значимые значения, а не постоянные 0s.

Второе предостережение: убедитесь, что вы используете возврат каретки при вводе данных в Windows.

При использовании System.in ваша программа получает входные данные только тогда, когда ваша командная оболочка передает их. Если вы используете перенаправление файлов / каналы (например, somefile > java myJavaApp или somecommand | java myJavaApp ), то входные данные обычно передаются немедленно. Однако, если вы вводите ввод вручную, передача данных может быть отложена. Например. В Windows cmd.exe shell данные буферизуются внутри cmd.exe shell. Данные передаются в исполняемую java-программу только после возврата каретки (control-m или <enter>). Это ограничение среды выполнения. Конечно, InputStream.available() будет возвращать 0 до тех пор, пока оболочка буферизует данные - это правильное поведение; на данный момент доступных данных нет. Как только данные становятся доступны из командной строки, метод возвращает значение > 0. ПРИМЕЧАНИЕ: Cygwin использует cmd.exe тоже.

Самое простое решение (без блокировки, поэтому таймаут не требуется)

Просто используйте это:

    byte[] inputData = new byte[1024];
int result = is.read(inputData, 0, is.available());
// result will indicate number of bytes read; -1 for EOF with no data read.

ИЛИ что-то подобное,

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in, Charset.forName("ISO-8859-1")),1024);
// ...
// inside some iteration / processing logic:
if (br.ready()) {
int readCount = br.read(inputData, bufferOffset, inputData.length-bufferOffset);
}

Более богатое решение (максимально заполняет буфер за период таймаута)

Объявите это:

public static int readInputStreamWithTimeout(InputStream is, byte[] b, int timeoutMillis)
throws IOException {
int bufferOffset = 0;
long maxTimeMillis = System.currentTimeMillis() + timeoutMillis;
while (System.currentTimeMillis() < maxTimeMillis && bufferOffset < b.length) {
int readLength = java.lang.Math.min(is.available(),b.length-bufferOffset);
// can alternatively use bufferedReader, guarded by isReady():
int readResult = is.read(b, bufferOffset, readLength);
if (readResult == -1) break;
bufferOffset += readResult;
}
return bufferOffset;
}

Тогда используйте это:

    byte[] inputData = new byte[1024];
int readCount = readInputStreamWithTimeout(System.in, inputData, 6000); // 6 second timeout
// readCount will indicate number of bytes read; -1 for EOF with no data read.
Ответ 2

Предполагая, что ваш поток не поддерживается сокетом (поэтому вы не можете использовать Socket.setSoTimeout()), я думаю, что стандартный способ решения проблем такого типа - использовать Future .

Предположим, у меня есть следующий исполнитель и потоки:

    ExecutorService executor = Executors.newFixedThreadPool(2);
final PipedOutputStream outputStream = new PipedOutputStream();
final PipedInputStream inputStream = new PipedInputStream(outputStream);

У меня есть writer, который записывает некоторые данные, затем ждет 5 секунд, прежде чем записать последний фрагмент данных и закрыть поток:

    Runnable writeTask = new Runnable() {
@Override
public void run() {
try {
outputStream.write(1);
outputStream.write(2);
Thread.sleep(5000);
outputStream.write(3);
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
};
executor.submit(writeTask);

Обычный способ чтения этого заключается в следующем. Чтение данных будет блокироваться на неопределенный срок, поэтому оно завершается за 5 секунд:

    long start = currentTimeMillis();
int readByte = 1;
// Read data without timeout
while (readByte >= 0) {
readByte = inputStream.read();
if (readByte >= 0)
System.out.println("Read: " + readByte);
}
System.out.println("Complete in " + (currentTimeMillis() - start) + "ms");

какие выходные данные:

Read: 1
Read: 2
Read: 3
Complete in 5001ms

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

    int readByte = 1;
// Read data with timeout
Callable<Integer> readTask = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return inputStream.read();
}
};
while (readByte >= 0) {
Future<Integer> future = executor.submit(readTask);
readByte = future.get(1000, TimeUnit.MILLISECONDS);
if (readByte >= 0)
System.out.println("Read: " + readByte);
}

какие выходные данные:

Read: 1
Read: 2
Exception in thread "main" java.util.concurrent.TimeoutException
at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:228)
at java.util.concurrent.FutureTask.get(FutureTask.java:91)
at test.InputStreamWithTimeoutTest.main(InputStreamWithTimeoutTest.java:74)

Я могу перехватить исключение TimeoutException и выполнить любую очистку, какую захочу.

Ответ 3

Если ваш InputStream поддерживается сокетом, вы можете установить таймаут сокета (в миллисекундах) с помощью setSoTimeout. Если вызов read() не разблокируется в течение указанного таймаута, он вызовет исключение SocketTimeoutException .

Просто убедитесь, что вы вызываете setSoTimeout в сокете, прежде чем выполнять вызов read() .

Ответ 4

Я бы поставил под сомнение формулировку проблемы, а не просто слепо принял ее. Вам нужны только таймауты с консоли или по сети. Если у вас есть последний, Socket.setSoTimeout() и HttpURLConnection.setReadTimeout() которые оба делают именно то, что требуется, при условии, что вы правильно настроили их при их создании / приобретении. Откладывать это до произвольной точки позже в приложении, когда все, что у вас есть, - это InputStream, - плохой дизайн, приводящий к очень неудобной реализации.

java