19 agosto, 2009

Control y pausa de ciclos for, do, while en Java

Un problema que sucede a menudo cuando se necesita procesar gran cantidad de informacion en un ciclo for o do/while es el de pausar el ciclo, esperar alguna señal y continuar.

for (condicion) {
    ....hacer algo...
    ....esperar....
    ....continuar.....
}

Por ejemplo, un problema al que me vi enfrentado fue el de ingresar el precio para una gran cantidad de productos, teniendo los productos en un Collection, parecia facil iterar sobre este listado, mostrar una ventana para que el usuario ingresara el precio y continuar:

Collection productos;
for (Producto p : productos) {
    ingresarPrecio(p);
}


A primera vista se ve simple, pero si este codigo es ejecutado, su resultado sera abrir tantas ventanas de ingreso como productos tenga el Collection.

La forma de ir mostrando las ventanas de forma ordenada, es pausar el ciclo for hasta que la ventana genere algun evento. Para ello lo primero sera hacer correr el ciclo dentro de un Thread diferente del actual de forma que pueda ser controlado sin interrumpir la ejecucion del programa:
Runnable hiloFor = new Runnable() {

    public void run() {
        for (Producto p : productos) {
            ingresarPrecio(p);
        }
    }
}

new Thread(hiloFor).start;


Esto lanzara nuestro ciclo for en un hilo distinto del actual, abriendonos la posibilidad de control. Para ello utilizaremos los metodos proporcionados por la clase Thread.

Lo que haremos a continuacion sera sincronizar el Thread donde vive nuestro ciclo for con el Thread principal donde se enta ejecutando nuestra aplicacion (en el caso de una aplicacion Swing sera el Event dispatching thread
Collection productos;
Thread hilo;

Runnable hiloFor = new Runnable() {

    public void run() {
        Thread actual = Thread.currentThread();
            synchronized(actual) {
                for (Producto p: Productos) {
                    ingresarPrecio(p);
                    actual.wait();
                }
            }
        }
}

hilo = new Thread(hiloFor);
hilo.start();


Puede notarse de inmediato los cambios introducidos: Primero obtenemos el objeto Thread donde se ejecuta nuestro for y luego ejecutamos el ciclo dentro de un bloque sincronizado con este hilo, invocamos el metodo que abre la ventana y pausamos la ejecucion del hilo.

En este punto el hilo donde se ejecuta el ciclo for estara pausado de forma indefinida esperando la señal notify(). Luego el metodo ingresarPrecio(Producto p) debera:


  • Abrir la ventana para ingresar el precio en el event dispatching thread

  • Notificar al hilo que puede continuar una vez se haya hecho el ingreso


Para ello, bastara que el metodo ingresarPrecio(Producto p) incluya en su interior algo similar a:


ingresarPrecio(final Producto p) {
    Runnable abrir = new Runnable() {

        public void run() {
            VentanaIngreso v = new VentanaIngreso(p);
            v.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    continuar();
                }
            });

        }

        private void continuar() {
            synchronized (hilo) {
                hilo.notify();
            }
        }
    }

    SwingUtilities.invokeLater(abrir);
}


La parte importante aca es el metodo continuar(). Aqui se le dice al hilo que continue ejecutandose. Como puede verse en este sencillo ejemplo, este metodo se llama desde un ActionListener que sera gatillado dentro de la ventana de ingreso ante ciertas acciones (cancelar, grabar el precio, etc).

El ejemplo puede mejorarse y extenderse a otras situaciones.


Bytes!

2 comentarios:

Anónimo dijo...
Este blog ha sido eliminado por un administrador de blog.
Anónimo dijo...

Gracias, muy interesante artículo .