Hilos y gvSIG (3 de 4): Estructura de nuestro proceso

En el articulo anterior, “Hilos y gvSIG (2 de 4): TaskStatus“, vimos las funcionalidades relacionadas con la utilidad TaskStatus, ahora vamos a ver cómo podríamos hacer para crear un proceso y ejecutarlo como un hilo de ejecución independiente de gvSIG que nos permita dejar al usuario seguir interactuando con el resto de la aplicación mientras éste se esta ejecutando. Para eso crearemos dos clases, MyExtension, que será una extensión de gvSIG encargada de lanzar el proceso y MyProcess, que estará implementado mediante un hilo de java.

En lo que respecta a nuestra extensión tendremos que tener encuenta algunas cosas:

  • Cada vez que el usuario lo solicite lanzará la ejecución de nuestro proceso en un hilo aparte.
  • Si se intenta ejecutar y el proceso ya está en ejecución presentará un mensaje al usuario indicando que en estos momentos ya está en ejecución nuestro proceso indicando que no podrá ejecutarlo hasta que éste termine.
  • La herramienta estará visible siempre, pero solo estará activa si no está nuestro proceso en ejecución.

Para esto nuestra extensión guardará una referencia a nuestro proceso, y en el método isEnabled comprobará si tenemos una instancia de nuestro proceso y si esta está viva a través del método isAlive de la clase Thread. Así mismo, en el execute se realizará una comprobación similar antes de lanzar la ejecución del proceso.

Veamos cómo seria el código de nuestra extensión:

public class MyExtension extends Extension {

  @SuppressWarnings("unused")
  private static Logger logger = LoggerFactory.getLogger(MyExtension.class);

  private MyProcess process = null;

  public void initialize() {

    // Do nothing
  }
  public void execute(String actionCommand) {

    if ("tools-devel-my-process".equalsIgnoreCase(actionCommand)) {
      if (process != null && process.isAlive()) {

        ApplicationManager application = ApplicationLocator.getManager();
        application.messageDialog(
            "Process already running. Wait to terminate.",

            "Test task", JOptionPane.WARNING_MESSAGE);
        return;
      }
      process = new MyProcess();

      process.start();
    }
  }
  public boolean isEnabled() {

    if (process != null && process.isAlive()) { 
      return false;

    }
    return true;
  }
  public boolean isVisible() {

    return true;
  }
}

Una vez ya tenemos preparada nuestra extensión veamos qué tendríamos en nuestra clase MyProcess. De forma general, asumiremos que nuestro proceso tendrá una serie de tareas previas, un bloque de tareas constituido por un bucle en el que a cada pasada se van ejecutando nuestro proceso y una serie de tareas a ejecutar a la finalización de éste. Así mismo, para el caso de ejemplo, voy a suponer que sé el número de iteraciones que va a hacer el proceso, lo que me permitirá informar a través del TaskStatus del avance del proceso. Tendremos definidos los siguientes métodos:

  • run, se tratará del hilo principal de ejecución. Contendrá el código principal de nuestro proceso haciendo las veces de main de éste.
  • preProcess, el método en el que englobaremos las tareas previas al bloque principales de nuestro proceso.
  • processItem, que será el encargado de procesar cada uno de los ítems de nuestro proceso.
  • postProcess, que realizará las tareas de finalización de nuestro proceso.

Los pasos a hacer en el método run serian:

  1. realización de la tareas previas:

    this.preProcess();
  2. Comprobar si se ha solicitado la cancelación del proceso:

    if (status.isCancellationRequested()) {
      status.cancel();
      return;
    
    }
  3. inicializar el status con el número de iteraciones que va a tener nuestro proceso:

    status.setRangeOfValues(1, this.maxValue);
  4. Iterar, comprobando al comienzo de cada iteración si se ha solicitado la cancelación
    del proceso y estableciendo el estado de progreso:

    for (int i = 1; i < this.maxValue; i++) {
    
      // Si ha sido solicitada la cancelacion de la tarea
      // la marcamos como cancelada y salimos del proceso.
      if (status.isCancellationRequested()) {
    
        status.cancel();
        return;
      }
      // Informamos del progreso de la tarea
      status.setCurValue(i);

    A cada iteración realizaremos las tareas propias de nuestro proceso:

    this.processItem(i);
  5. Realizar las tareas de post-proceso:

    this.postPrecess();
  6. Comprobar si se ha solicitado la cancelación durante la ejecución de las tareas
    de post-proceso:

    if (status.isCancellationRequested()) {
      status.cancel();
    
      return;
    }
  7. A la terminación de nuestro proceso marcar el status como terminado.
    Normalmente esto lo haremos en la instrucción finally, para asegurarnos
    que a la salida de nuestro proceso siempre se actualiza el estado de
    éste:

    } finally {
    
      if (status != null) {
        // Mark process as terminate if it is running.
        if (status.isRunning()) {
    
          status.terminate();
        }
      }
    }

Es importante tener en cuenta algunas consideraciones:

  • Deberemos consultar si se ha pedido la cancelación de nuestro proceso, a cada iteración de éste, o si se trata de un proceso muy pesado, con un alto número de iteraciones, cada cierto número de éstas que sea razonable en tiempo de usuario.
  • Deberemos atrapar los errores de forma adecuada. Hay que tener en cuenta que si éstos se producen no se propagarán hasta el interface de usuario de la aplicación, y el usuario puede no enterarse de problemas durante la ejecución de nuestro proceso.
  • Deberemos marcar nuestro proceso como terminado a su finalización, lo que normalmente haremos con la clausula finally-
  • No podremos interactuar con el interface de usuario directamente. Swing no está preparado para trabajar en multihilo, así que solo podremos hacerlo a través de métodos que estén preparados para ello o utilizando los métodos invokeLater e invokeAndWait de la clase SwingUtilities de swing tal como vimos en el ejemplo de la clase MyObserver.

Con todas estas consideraciones en mente veamos como podría quedar nuestra clase MyProcess :

public class MyProcess  extends AbstractMonitorableTask {

  private static Logger logger = LoggerFactory.getLogger(MyProcess.class);

  private ApplicationManager application = null;
  private int maxValue = 100;

  private long sleepTime = 100;

  protected MyProcess() {

    super("MyProcess");
    this.application = ApplicationLocator.getManager();

  }
  public void run() {
    SimpleTaskStatus status = null;

    try {
      status = (SimpleTaskStatus) this.getTaskStatus();

      this.preProcess();
      if (status.isCancellationRequested()) {

        status.cancel();
        return;
      }
      status.setRangeOfValues(1, this.maxValue);

      for (int i = 1; i < this.maxValue; i++) {

        // Si ha sido solicitada la cancelacion de la tarea
        // la marcamos como cancelada y salimos del proceso.
        if (status.isCancellationRequested()) {

          status.cancel();
          return;
        }
        // Informamos del progreso de la tarea
        status.setCurValue(i);

        this.processItem(i);
      }
      this.postPrecess();

      if (status.isCancellationRequested()) {
        status.cancel();

        return;
      }
      application.message("Process terminated", JOptionPane.INFORMATION_MESSAGE);

    } catch (Exception e) {
      logger.info("Error in process", e);

      if (status != null) {
        status.abort();

      }
      application.message("Process error", JOptionPane.WARNING_MESSAGE);
      application.messageDialog(

              "Se ha producido un error realizando el proceso y este terminara de forma inesperada.\n\n"
                  + e.getMessage(), this.getName(),
              JOptionPane.WARNING_MESSAGE);

    } finally {
      if (status != null) {

        // Mark process as terminate if it is running.
        if (status.isRunning()) {
          status.terminate();

        }
      }
    }
  }
  private void preProcess() {

    ...
  }
  private void processItem(int i) throws InterruptedException {

    ...
  }
  private void postPrecess() {
    ...

  }
}

Tal como se han descrito las clases MyExtension y MyProcess, éstas podrían quedar como un esquema general a la hora de implementar procesos en gvSIG. Las adaptaríamos a nuestras necesidades y las podríamos utilizar de cara a construir nuestros procesos de forma que no fuesen bloqueantes respecto al interface de gvSIG.

En el próximo articulo, “Hilos y gvSIG (4 de 4): Interactuando con el usuario“, vamos a seguir viendo cómo podemos abordar algunas cosas más relacionadas con el manejo de hilos, y veremos en un ejemplo sencillo qué podemos tener en los metodos preProcess y processItem, y cómo podríamos hacer para interactuar desde ellos con el interfaz de usuario en caso de que lo necesitásemos.

Un saludo

Joaquin

About Joaquin del Cerro

Development and software arquitecture manager at gvSIG Team. gvSIG Association
This entry was posted in development, gvSIG Desktop, spanish and tagged . Bookmark the permalink.

2 Responses to Hilos y gvSIG (3 de 4): Estructura de nuestro proceso

  1. Pingback: Hilos y gvSIG (2 de 4): TaskStatus | gvSIG blog

  2. Pingback: Hilos y gvSIG (4 de 4): Interactuando con el usuario | gvSIG blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s