Я создаю приложение для университетского проекта. При создании TUI необходимо напрямую использовать терминал для получения входных данных и их отображения. Я хотел добиться следующего, а не блокировать основной поток, в котором я должен прослушивать ввод, чтобы иметь возможность получать асинхронную информацию и выдавать исключение, которое фактически возвращало бы пользователя на стартовый экран. Мой код для этого следующий:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* Test class for asynchronous input
* Please note that while running in CMD the input is actually visualized AFTER pressing enter.
*/
public class Main
{
public static final int SLEEP_MILLIS = 200;
public static void main (String[] args) {
String input = "";
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Test asynchronous input started, write anything below:");
while(!input.toLowerCase().matches("q|quit|exit")) {
try {
// wait until we have data to complete a readLine()
while (!reader.ready()) {
Thread.sleep(SLEEP_MILLIS);
//the other asynchronous condition goes here
}
input = reader.readLine();
} catch (InterruptedException | IOException ignored) {
System.err.println("ERROR OF READER!");
}
System.out.println("INPUT RECEIVED: " + input);
}
}
}
Метод BufferedReader.ready() должен возвращать false if the buffer is empty or true if the buffered reader is ready, как указано в документации.
Насколько я понял, приложение должно иметь возможность циклически работать, пока пользователь ничего не пишет. На самом деле на терминале Linux (с использованием WSL) все работает корректно, вот скриншот: Как приложение выглядит на терминале Linux
Проблема, с которой столкнулись я и моя команда, заключается в том, что в cmd и powershell кажется, что терминал неправильно отображает вводимые пользователем данные. Я имею в виду, что если пользователь что-то пишет, а затем вводит команду, все работает нормально, даже отображение работает правильно. Единственная проблема заключается в том, что вводимые пользователем данные не отображаются.
Мы попытались изменить его с while на if. Мы попытались отключить спящий режим, так как думали, что это может нарушить управление потоками Windows. Больше идей у нас не было, потому что мы понятия не имели, в чем может быть проблема, поскольку в Linux все работает нормально.
Единственное, что, возможно, могло бы решить мою проблему, я нашел в этом обсуждении: как читать со стандартного ввода неблокируя?. Хотя это не совсем моя проблема.
Обновлено: заменил приведенный выше код минимальным воспроизводимым примером, в котором описана точно такая же проблема.
Можете ли вы воспроизвести проблему на минимально воспроизводимом примере? Было бы очень полезно, если бы мы могли протестировать это на наших собственных системах.
@user85421 user85421, тогда как можно ждать ввода пользователя без необходимости блокироваться на readLine?? Или как мы можем заставить оболочку отображать символы? Мы не можем ждать ввода пользователя во время проверки состояния, если не использовать больше потоков только для получения ввода пользователя. Я имею в виду, что странно, что это происходит только в Windows, мы должны протестировать это и на Mac, хотя, используя Linux-подобный терминал, я полагаю, это будет вести себя так же, как и в Linux.
рассмотрите возможность использования второго потока, ожидающего ввода, и, если он задан, записывает его в некоторую очередь, которая может быть прочитана основным (или другим) потоком («ожидание» и «без блокировки» являются своего рода противоположностями)
AFAIK, использование ready() - неправильный инструмент, если он возвращает false, и вы больше ничего не делаете с программой чтения, она продолжит возвращать false, потому что она никогда не заполнит буфер. Кроме того, почему бы просто не читать, это блокирует и лучше, чем просто спать.
@MarkRotteveel, значит, это ничего не значит, если кто-то пишет в system.in, а поток никогда не достигает readLine()? Для того чтобы BufferReader мог добраться до другой функции и узнать, заполняется ли она, необходим еще один поток? Я предположил, что, поскольку BufferedReader находился в System.in, он будет уведомлен, как только какой-то символ начнет помещаться в буфер, когда пользователь начнет писать.
@user85421 user85421 Что, если я просто использую ветку для чтения? Насколько я знаю, поток, ожидающий чтения, будет разбужен сигналом прерывания от ОС. Это означает, что функция Ready() завершится ошибкой до тех пор, пока поток, ожидающий readLine(), не будет разбужен сигналом прерывания. Возможно ли, что я не совсем не прав? Или поток будет пробужден, когда будет заполнена вся строка (в основном, когда пользователь вводит символ новой строки)? Или может быть и то, и другое? Извините, но если мне удастся лучше понять, я, возможно, даже не буду использовать очередь, поскольку это будет неоптимально.
@GabrieleSantandrea Нет, буферизация основана на извлечении (т. е. когда вы (пытаетесь) что-то прочитать), а не на принудительном
@Gabriele «Что, если я просто использую ветку для чтения?» - это то, что я предложил в предыдущем комментарии




Я попробовал этот поток с подходом BlockingQueue, и он решил проблему. Все отображается корректно на любой консоли.
Спасибо @user85421 и @MarkRotteveel за предложение.
Это решение имеет дополнительное преимущество, заключающееся в немедленной обработке входных данных даже при высоких значениях SLEEP_MILLIS.
import java.util.Scanner;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.*;
/**
* Test class for asynchronous input
*/
public class MainThreaded
{
public static final int SLEEP_MILLIS = 200;
public static final Object LOCK = new Object();
public static boolean breakIfTrueAsynchronous = false;
public static void main (String[] args) {
BlockingQueue<String> inputQueue = new LinkedBlockingDeque<>();
Scanner scanner = new Scanner(System.in);
ExecutorService executor = Executors.newSingleThreadExecutor();
Runnable inputReader = () -> {
while(true) {
String str = scanner.nextLine();
inputQueue.add(str);
}
};
executor.execute(inputReader);
//run a timer to get the asynchronous error every 3 sec
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
synchronized (LOCK) {
breakIfTrueAsynchronous = true;
}
}
}, 3000, 3000);
System.out.println("Test asynchronous input started, write anything below:");
String input = "";
while(true) { // use ctrl+C to exit
try {
input = inputQueue.poll(SLEEP_MILLIS, TimeUnit.MILLISECONDS);
if (input == null) input = ""; // if timeout elapses, avoid null value
synchronized (LOCK){
if (breakIfTrueAsynchronous){
System.err.println("BREAK RECEIVED");
//here our code would throw Exception and do something else
breakIfTrueAsynchronous = false;
}
}
} catch (InterruptedException ignored) {
System.err.println("ERROR OF READER!");
}
if (!input.isEmpty()) {
//use input here
System.out.println("INPUT RECEIVED: " + input);
input = "";
}
}
}
}
Обновлено: я добавил таймер для запуска условия асинхронного останова в целях тестирования.
EDIT2: Это решение создает другую проблему в нашем проекте. Здесь я разместила ещё вопрос если кому интересно.
больше похоже на проблему Windows — если код ничего не делает, просто
Thread.sleep()вы не сможете увидеть ничего из набранного; на самом деле все набранное будет отправлено в оболочку после завершения программы