Командная строка, похоже, не отображает символы System.in при использовании метода BufferedReader.ready()

Я создаю приложение для университетского проекта. При создании 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 все работает нормально.

Единственное, что, возможно, могло бы решить мою проблему, я нашел в этом обсуждении: как читать со стандартного ввода неблокируя?. Хотя это не совсем моя проблема.

Обновлено: заменил приведенный выше код минимальным воспроизводимым примером, в котором описана точно такая же проблема.

больше похоже на проблему Windows — если код ничего не делает, просто Thread.sleep() вы не сможете увидеть ничего из набранного; на самом деле все набранное будет отправлено в оболочку после завершения программы

user85421 30.05.2024 22:18

Можете ли вы воспроизвести проблему на минимально воспроизводимом примере? Было бы очень полезно, если бы мы могли протестировать это на наших собственных системах.

markspace 30.05.2024 22:27

@user85421 user85421, тогда как можно ждать ввода пользователя без необходимости блокироваться на readLine?? Или как мы можем заставить оболочку отображать символы? Мы не можем ждать ввода пользователя во время проверки состояния, если не использовать больше потоков только для получения ввода пользователя. Я имею в виду, что странно, что это происходит только в Windows, мы должны протестировать это и на Mac, хотя, используя Linux-подобный терминал, я полагаю, это будет вести себя так же, как и в Linux.

user25335987 31.05.2024 08:54

рассмотрите возможность использования второго потока, ожидающего ввода, и, если он задан, записывает его в некоторую очередь, которая может быть прочитана основным (или другим) потоком («ожидание» и «без блокировки» являются своего рода противоположностями)

user85421 31.05.2024 09:48

AFAIK, использование ready() - неправильный инструмент, если он возвращает false, и вы больше ничего не делаете с программой чтения, она продолжит возвращать false, потому что она никогда не заполнит буфер. Кроме того, почему бы просто не читать, это блокирует и лучше, чем просто спать.

Mark Rotteveel 31.05.2024 10:12

@MarkRotteveel, значит, это ничего не значит, если кто-то пишет в system.in, а поток никогда не достигает readLine()? Для того чтобы BufferReader мог добраться до другой функции и узнать, заполняется ли она, необходим еще один поток? Я предположил, что, поскольку BufferedReader находился в System.in, он будет уведомлен, как только какой-то символ начнет помещаться в буфер, когда пользователь начнет писать.

user25335987 31.05.2024 11:08

@user85421 user85421 Что, если я просто использую ветку для чтения? Насколько я знаю, поток, ожидающий чтения, будет разбужен сигналом прерывания от ОС. Это означает, что функция Ready() завершится ошибкой до тех пор, пока поток, ожидающий readLine(), не будет разбужен сигналом прерывания. Возможно ли, что я не совсем не прав? Или поток будет пробужден, когда будет заполнена вся строка (в основном, когда пользователь вводит символ новой строки)? Или может быть и то, и другое? Извините, но если мне удастся лучше понять, я, возможно, даже не буду использовать очередь, поскольку это будет неоптимально.

user25335987 31.05.2024 11:15

@GabrieleSantandrea Нет, буферизация основана на извлечении (т. е. когда вы (пытаетесь) что-то прочитать), а не на принудительном

Mark Rotteveel 31.05.2024 11:31

@Gabriele «Что, если я просто использую ветку для чтения?» - это то, что я предложил в предыдущем комментарии

user85421 31.05.2024 12:04
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
9
98
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я попробовал этот поток с подходом 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: Это решение создает другую проблему в нашем проекте. Здесь я разместила ещё вопрос если кому интересно.

Другие вопросы по теме

Как передать PIL.Image как поток изображения в ComputerVisionClient read_in_stream
Почему проверка isEmpty каждой строки во время BufferedReader имеет значение при чтении файла Java построчно
Есть ли какой-либо другой метод, который я могу использовать для чтения строк в моем коде для выполнения функции readLine()?
Есть ли способ прочитать одну строку и следующую, перебирая файл .txt в Java?
Получение ввода в разных строках с помощью токенизатора строк
Есть ли способ избежать «нажатия клавиши ввода» при чтении строк из стандартного ввода с помощью BufferedReader?
Цикл while не прерывается после чтения нескольких строк текста с помощью BufferedReader в Java Socket
Буферизованный ридер не сбрасывает символы в буфер при достижении EOF
Почему возникла преждевременная ошибка EOF при чтении CSV-файла с помощью запланированной задачи SpringBoot
Исключение в потоке "main" java.io.IOException: поток закрыт: класс BufferedReader