java - Interrumpir escáner nextLine() (alternativas)

CorePress2024-01-25  7

Tengo el siguiente hilo:

Thread t1 = new Thread() {
    @Override
    public void run() {
        while (!progress.equals(duration)) {
            try {
                Thread.sleep(1000);
                progress = progress.plusSeconds(1);
                // synchronized (this) { while (paused) { this.wait(); } }
            } catch (InterruptedException e) {
                interrupt();
            }
        }
    }
};
t1.start();

Estoy intentando implementar una funcionalidad que permita al usuario pausar y detener este hilo usando la consola. Básicamente, esto:

Scanner sc = new Scanner(System.in);
int choice;

while (t1.isAlive()) {
    System.out.println("Choose an option:\n1. Pause/Resume\n2. Stop");
    choice = Integer.parseInt(sc.nextLine());
    // if (choice == 1) { ... } else if (choice == 2) { t1.interrupt() }
    // synchronized (t1) { t1.notify(); }
}

Mi problema es que una vez que t1 muere, t1.isAlive() se evalúa como falso, pero el programa no sale del ciclo while porque está atascado esperando una última entrada del usuario. Quiero interrumpir sc.nextLine(), pero leí que no es posible porque el hilo está bloqueado. ¿Cómo podría hacer esto?

Probé lo siguiente:

Thread t2;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

while (t1.isAlive()) {
    t2 = new Thread() {
        @Override
        public void run() {
            try {
                while (!br.ready())
                    Thread.sleep(200);
                choice = Integer.parseInt(br.readLine());
            } catch (InterruptedException e) {
            } catch (IOException e) {
            }
        }
    };
    t2.start();
}

Supuestamente, esto debería permitirme interrumpir t2, pero debo estar haciendo algo mal porque sigue imprimiendo. Elija una opción: 1. Pausa/Reanudar 2. Detener, así que no puedo comprobar si funciona.

Bueno, primero la pregunta obvia. ¿Por qué hacer las cosas de esta manera cuando ExecutorService y Future ya proporcionan una forma confiable de probar si un subproceso se está ejecutando y detenerlo si es necesario?

-espacio de marca

27/03/2021 a las 15:25

Esta es la primera vez que trabajo con subprocesos y comencé a aprender a codificar no hace mucho, así que no sé qué hacen ExecutorService y Future. ¿Usarlos complicaría mi código?¿demasiado? Porque estoy tratando de mantenerlo lo más simple posible, ya que esta es una respuesta a un ejercicio que dice usar "hilos de tiempo simples" para solucionar esto.

-Cristina

27/03/2021 a las 15:43

Bien, aprender los conceptos básicos está bien para usar lenguajes primitivos como este. La siguiente pregunta que tengo es Supongo que cuál es tu objetivo. t1 no llama a ningún método de escáner, por lo que no estoy seguro de cómo espera que una interrupción lo pause/detenga. Creo que un poco más de explicación de lo que realmente estás tratando de hacer podría ayudarte a obtener respuestas. Intenta incluir la mayor parte del ejercicio real.ise para que podamos ver lo que realmente está pasando.

-espacio de marca

27/03/2021 a las 15:53

El propósito del programa es: primero, un usuario "inicia sesión" ingresar alguna información (se crea una instancia de Usuario y se agrega a una ArrayList); entonces, ese usuario podrá "cargar" un vídeo (se crea una instancia de Vídeo); finalmente, el usuario puede "jugar" el video (esto es lo que t1 simula, que el video se está reproduciendo), y deberían poder pausarlo/detenerlo mientras se reproduce. Funciona bien, pero una vez que termina de reproducirse el video, el programa no sale delMientras se realiza el bucle (t1.isAlive()), se atasca esperando una última entrada. Lo que quiero saber es si existe una forma sencilla de solucionar este problema.

-Cristina

27/03/2021 a las 17:42

Solucionar esto = salir del bucle while sin tener que ingresar nada más porque no tiene sentido hacerlo si el video ha terminado de reproducirse y para que el usuario pueda subir otro video si lo desea o reproducir el video nuevamente

-Cristina

27/03/2021 a las 17:46



------------------------------------

La cuestión crucial es que la API de System.in no ofrece garantías. Una JVM puede cumplir todas las especificaciones de JVM incluso si tiene un System.in tal que, si se interrumpe, no pasa nada y, de hecho, es completamente imposible interrumpir System.in, aparte de System.exit.

Sin embargo, afortunadamente la mayoría de las implementaciones de JVM no funcionan de esa manera: si activas el indicador de interrupción en cualquier hilo determinado, sucederán 3 cosas:

Cualquier método que esté especificado para mirarlos definitivamente será interrumpido: todos estos son métodos que están declarados para lanzar InterruptedException. Todos estos métodos, si se activa el indicador de interrupción del subproceso, dejarán de esperar inmediatamente, bajarán el indicador y regresarán lanzando InterruptedException. Sí, esto significa que si primero levantas el indicador de interrupción (someThread.interrupt() levanta el indicador y no hace nada más; son otros métodos que lo analizan los que hacen que la magia funcione), y luego invocas, por ejemplo. Thread.sleep, las llamadas de suspensión regresan inmediatamente (al lanzar InterruptedEx) y no esperan ni un solo milisegundo.

Los métodos que pausan un hilo pero que no están especificados para tratarlo correctamente están en el limbo: depende de la implementación del tiempo de ejecución de Java si algo sucede. Sin embargo, normalmente algo sucederá. Estos métodos casi siempre arrojan algún tipo de excepción marcada (para conexiones de base de datos, SQLEx, para operaciones de red, archivos y tuberías, IOException); cualquier código que esté actualmente esperando para enviar o recibir datos sobre una de estas cosas se ocupará deun indicador de interrupción elevado bajando el indicador, abortando la operación y regresando lanzando esa excepción marcada con un mensaje que indica que se produjo una interrupción.

Si se está ejecutando un código que no responde en absoluto al indicador de interrupción, entonces no sucede nada: el indicador permanece elevado y la JVM no hará nada más; El objetivo del indicador de interrupción es que simplemente se activa y luego se espera hasta que el hilo ejecute el código que lo analiza. Con suerte, eso sucederá muy pronto, pero no hay garantías.

Eso significa que lo más probable es que todo lo que necesites hacer sea:

En T1 Tener algún tipo de objeto AtomicBoolean que t1 establecerá en verdadero una vez que se complete el trabajo. t1 también activará la bandera de interrupción de t2 cuando se complete el trabajo. En T2

Protegersu llamada readLine() colocándola en un bloque try/catch, detectando IOException. Si hay un bucle, es posible que también desee considerar verificar usted mismo el indicador de interrupción, en caso de que esté configurado entre invocaciones de readLine(); Esto se hace con Thread.interrupted(), que devuelve verdadero y baja la bandera si está levantada. Generalmente, algo como while (!Thread.interrupted() && otras condiciones) { /* bucle principal aquí */ }.

En el controlador de captura IOException, verifique el indicador 'hemos terminado' de t1 (ese AtomicBoolean). Si dice "hemos terminado", entonces interprete el IOEx como una simple notificación de que el trabajo está terminado (por lo tanto, no lo registre en ningún lado; esperaba que sucediera). Sin embargo, si el indicador "hemos terminado" aún no está configurado, entonces esa IOException indica un problema de E/S real con la entrada.tubería, lo que puede suceder, por supuesto. Debe proceder normalmente (lo que generalmente significa lanzarlo hacia adelante para que la aplicación falle con un registro completo, no puede responder sensatamente a que la tubería de entrada tenga problemas de E/S, excepto salir con información de depuración sobre lo que sucedió). Entonces, simplemente lanza esa IOException. Si no puedes, lanza una nueva UncheckedIOException(thatIoException); es lo que estás buscando.

La advertencia

Desafortunadamente, sólo porque funcione en su sistema no significa que funcionará en cualquier otro lugar. Como dije, en algunas VM implica que System.in.read() simplemente no se puede interrumpir, punto. No hay nada que puedas hacer, aparte de pasos extremadamente drásticos: dejar de ser una aplicación de línea de comandos y mostrar una ventana GUI oscilante o convertirla en una aplicación web de algún tipo.

Notas finales

listo() y disponible() están casi completosmuy inútil. No están rotos, en el sentido de que hacen exactamente lo que su javadoc dice que hacen estos métodos, pero si lees detenidamente ese javadoc, te darás cuenta de que lo que proporcionan es completamente inútil. La única forma real de saber si los datos están disponibles es intentar leerlos, lo que luego te lleva a la trampa de: Bueno, en algunas plataformas, eso no se puede interrumpir. Sí. Apesta. No hay disponible ninguna solución confiable, en el sentido de que la API garantice que funcionará en todas las plataformas. El 99,5% de todo el código que llama a estos métodos está roto. Es muy poco probable que alguna vez quieras llamar a estos métodos.



------------------------------------

Parece un tema inocente, pero en realidad es un poco más complicado. Cuando lees desde la entrada estándar, generalmente terminas en una llamada al sistema operativo. Que no regresará hasta que tenga una entrada real con la que regresar y no tenga idea sobre el mecanismo de interrupción de Java. Aquí se describe como un subproducto.

Lo que puedes hacer es proporcionar tu propio InputStream en lugar de usar System.in directamente e implementar su método read() de manera que entre en System.in.read() solo cuando System.in.available() lo dice. Hasta entonces, simplemente repita la verificación con cierto retraso, como usar Thread.sleep() que está preparado para ser interrumpido de todos modos:

public static void main(String[] args) {
    Thread main = Thread.currentThread();
//  try (Scanner sc = new Scanner(System.in)) {
    try (Scanner sc = new Scanner(new InputStream() {
        @Override
        public int read() throws IOException {
            while (System.in.available() == 0)
                try {
                    Thread.sleep(100);
                } catch (InterruptedException ie) {
                    throw new IOException();
                }
            return System.in.read();
        }
    })) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException ie) {
                }
                main.interrupt();
            }
        }).start();
        String line = sc.nextLine();
        System.out.println(line);
        System.out.println(main.isInterrupted());
    } catch (Exception ex) {
        System.out.println("Time's up, probably. Actual exception: " + ex);
        System.out.println(main.isInterrupted());
    }
}

Si comentas el try(Scanner...-})) { bloquea y descomenta el pecadovariante de línea simple, puedes probar cómo no funciona en sí mismo: siempre tendrás que ingresar algo, solo el resultado de System.out.println(main.isInterrupted()); te dirá si lo hiciste en 5 segundos o te tomó más tiempo.

Nota al margen: en tu propio intento estabas interrumpiendo el subproceso del temporizador, en su lugar necesitas una referencia al otro subproceso, aquí en este ejemplo esa es la variable principal del subproceso.

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare