2010-07-05 7 views
5

Tengo un método que, conceptualmente, se ve algo como:Cómo escribir métodos interrumpible

Object f(Object o1) { 
    Object o2 = longProcess1(o1); 
    Object o3 = longProcess2(o2); 
    return longProcess3(o3); 
} 

Cuando los procedimientos mismos también podrían estar compuesto de:

Object longProcess1(Object o1) { 
    Object o2 = longSubProcess1(o1); 
    return longSubProcess2(o2); 
} 

Y así sucesivamente, con los diferentes procesos potencialmente sentados en diferentes módulos. La mayoría de los procesos son largos porque son computacionalmente costosos, no vinculados a IO.

Hasta ahora todo bien, pero ahora quiero que f en su totalidad sea interrumpible. The recommended Java way to do that es verificar periódicamente la bandera interrumpida con Thread.interrupted(). Es bastante sencillo, pero rápidamente puede llegar a ser engorroso si necesito cambiar mis métodos para algo como:

Object f(Object o1) { 
    Object o2 = longProcess1(o1); 
    if (Thread.interrupted()) throw new InterruptedException(); 
    Object o3 = longProcess2(o2); 
    if (Thread.interrupted()) throw new InterruptedException(); 
    return longProcess3(o3); 
} 

Object longProcess1(Object o1) { 
    Object o2 = longSubProcess1(o1); 
    if (Thread.interrupted()) throw new InterruptedException(); 
    return longSubProcess2(o2); 
} 

... 

Ahora, entiendo lo racional para trabajar de esa manera - que me permite un mejor control cuando el InterruptedException (por ejemplo) se lanzará, evitando dejar objetos en estados inconsistentes, pero Tengo curiosidad por saber si hay una manera más elegante de hacerlo *.

* En Java, no AspectJ, que supongo que es muy apropiado aquí, pero estoy atascado con Java.

+0

Estaría preocupado por los puntos muertos con código que contiene hilos que Ref entre sí. –

Respuesta

7

Se podría utilizar un interfaz y un proxy dinámico:

public class Wrapper { 
    public static <T> T wrap(Class<T> intf, final T impl) { 
     ClassLoader cl = Thread.currentThread().getContextClassLoader(); 
     Object proxy = Proxy.newProxyInstance(cl, new Class<?>[] {intf}, 
       new InvocationHandler() { 
      public Object invoke(Object proxy, Method method, Object[] args) 
        throws Throwable { 
       if (Thread.interrupted()) { 
        throw new InterruptedException(); 
       } 
       return method.invoke(impl, args); 
      } 
     }); 
     return intf.cast(proxy); 
    } 
} 

interface Processes { 
    Object longProcess1(Object o); 
    ... 
} 

public class ProcessesImpl implement Processes { 
    Processes self = Wrapper.wrap(Processes.class, this); 

    public Object f(Object o1) { 
     Object o2 = self.longProcess1(o1); 
     Object o3 = self.longProcess2(o2); 
     return self.longProcess3(o3); 
    } 

    public Object longProcess1(Object o1) { 
     Object o2 = self.longSubProcess1(o1); 
     return self.longSubProcess2(o2); 
    } 

    .... 
} 
+0

Interesante, pero aún un poco engorroso. Tendría que agregar un campo para todas las clases relevantes y calificar muchas llamadas a métodos. Los métodos * resultantes son * más elegantes, sin embargo, ¡te daré eso! – Oak

0

he llegado hasta este derecho que secuencialmente ejecutar métodos que están en el mismo nivel de anidamiento? Si es así, ¿por qué no simplemente implementa tus métodos de cómputo como instancias java.lang.Runnable, organízalos en listas e inícialos en el ciclo? Entonces solo tendrías un lugar con Thread.interrupted() cheque.

Puede considerar usar java.util.concurrent.ExecutorService para facilitar el control sobre las tareas de computación.

actualiza con un ejemplo:

import java.util.ArrayList; 
import java.util.List; 

public class Test { 

    public static void main(String[] args) { 
     List<CompoundProcess> subProcesses1 = new ArrayList<CompoundProcess>(); 
     subProcesses1.add(new CompoundProcess() { 
      public void run() { 
       System.out.println("Process 1.1"); 
      } 
     }); 
     subProcesses1.add(new CompoundProcess() { 
      public void run() { 
       System.out.println("Process 1.2"); 
      } 
     }); 

     List<CompoundProcess> subProcesses2 = new ArrayList<CompoundProcess>(); 
     subProcesses2.add(new CompoundProcess() { 
      public void run() { 
       System.out.println("Process 2.1"); 
      } 
     }); 
     subProcesses2.add(new CompoundProcess() { 
      public void run() { 
       System.out.println("Process 2.2"); 
      } 
     }); 

     List<CompoundProcess> processes1 = new ArrayList<CompoundProcess>() {}; 
     processes1.add(new CompoundProcess(subProcesses1)); 
     processes1.add(new CompoundProcess(subProcesses2)); 

     CompoundProcess process = new CompoundProcess(processes1); 
     process.run(); 
    } 


    static class CompoundProcess implements Runnable { 

     private List<CompoundProcess> processes = new ArrayList<CompoundProcess>(); 

     public CompoundProcess() { 
     } 

     public CompoundProcess(List<CompoundProcess> processes) { 
      this.processes = processes; 
     } 

     public void run() { 
      for (Runnable process : processes) { 
       if (Thread.interrupted()) { 
        throw new RuntimeException("The processing was interrupted"); 
       } else { 
        process.run(); 
       } 
      } 
     } 
    } 

} 
+0

No, los procesos están en muchos lugares diferentes. Intenté demostrarlo mostrando que 'longProcess1' está en sí mismo compuesto, tal vez no era lo suficientemente claro, así que he editado la pregunta para aclarar. – Oak

+0

Una vez que los procesos en el mismo nivel se ejecutan secuencialmente, no importa mucho que se inicien en lugares diferentes. Actualicé mi respuesta con un ejemplo. Puede anidar procesos al nivel que desee. – fnt

Cuestiones relacionadas