2010-01-29 10 views
19

Tengo un código que envía una solicitud a otro hilo que puede o no enviar esa solicitud a otro hilo. Eso produce un tipo de devolución de Future<Future<T>>. ¿Hay alguna forma no atroz de convertir esto inmediatamente en Future<T> que espera la finalización de toda la cadena futura?¿Hay alguna manera fácil de convertir Future <Future<T>> en Future <T>?

Ya estoy usando la biblioteca de Guava para manejar otras cosas divertidas de concurrencia y como reemplazo de Google Collections y su funcionamiento, pero parece que no puedo encontrar algo para este caso.

+4

Sería útil si pudiera agrega un poco más de contexto. La respuesta obvia es llamar a get(), pero eso probablemente no es lo que quieres. –

+0

Hecho. Lo siento, no estaba claro. – Nik

+0

Parece que necesita Monad – user

Respuesta

5

Guava 13.0 agrega Futures.dereference para hacer esto. Requiere un ListenableFuture<ListenableFuture>, en lugar de un simple Future<Future>. (Operar en un Future llano requeriría una llamada makeListenable, cada uno de los cuales requiere un hilo dedicado para la vida de la tarea (como se aclara con el nuevo nombre del método, JdkFutureAdapters.listenInPoolThread).

0

Se puede crear una clase como:

public class UnwrapFuture<T> implements Future<T> { 
    Future<Future<T>> wrappedFuture; 

    public UnwrapFuture(Future<Future<T>> wrappedFuture) { 
     this.wrappedFuture = wrappedFuture; 
    } 

    public boolean cancel(boolean mayInterruptIfRunning) { 
     try { 
      return wrappedFuture.get().cancel(mayInterruptIfRunning); 
     } catch (InterruptedException e) { 
      //todo: do something 
     } catch (ExecutionException e) { 
      //todo: do something 
     } 
    } 
    ... 
} 

Vas a tener que lidiar con las excepciones que se interponen() puede aumentar, pero otros métodos no pueden.

+1

Eso es más o menos lo que estaba tratando de evitar. Además, si cancela el método que tiene allí, cancelará la espera hasta que finalice el primer futuro de la cadena. Eso definitivamente no es lo que estoy buscando. – Nik

+2

"conviértalo en Future que espera la finalización de toda la cadena futura?" ... No creo que pueda cancelar el segundo futuro hasta que lo tenga. Pero no puedes obtenerlo hasta que el primer futuro lo devuelva. – Dave

+0

Buena captura. Si bien el segundo futuro es uno creado por el primero, estoy seguro de que podrías ponerte en un estado en el que hayas cancelado el primer futuro, pero de todos modos es el segundo y no puedes cancelarlo. Apuesto a que podrías arreglar eso con 'Futures.makeListenable'-el primer futuro y agregar un oyente que cancela inmediatamente el futuro encadenado a la vuelta. El problema luego se convierte en prueba para ese caso. – Nik

0

Esta fue mi primera puñalada, pero estoy seguro de que hay muchas fallas. Estaría más que feliz de simplemente reemplazarlo con algo como Futures.compress(f).

public class CompressedFuture<T> implements Future<T> { 
    private final Future<Future<T>> delegate; 

    public CompressedFuture(Future<Future<T>> delegate) { 
     this.delegate = delegate; 
    } 

    @Override 
    public boolean cancel(boolean mayInterruptIfRunning) { 
     if (delegate.isDone()) { 
      return delegate.cancel(mayInterruptIfRunning); 
     } 
     try { 
      return delegate.get().cancel(mayInterruptIfRunning); 
     } catch (InterruptedException e) { 
      throw new RuntimeException("Error fetching a finished future", e); 
     } catch (ExecutionException e) { 
      throw new RuntimeException("Error fetching a finished future", e); 
     } 
    } 

    @Override 
    public T get() throws InterruptedException, ExecutionException { 
     return delegate.get().get(); 
    } 

    @Override 
    public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 
     long endTime = System.currentTimeMillis() + unit.toMillis(timeout); 
     Future<T> next = delegate.get(timeout, unit); 
     return next.get(endTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); 
    } 

    @Override 
    public boolean isCancelled() { 
     if (!delegate.isDone()) { 
      return delegate.isCancelled(); 
     } 
     try { 
      return delegate.get().isCancelled(); 
     } catch (InterruptedException e) { 
      throw new RuntimeException("Error fetching a finished future", e); 
     } catch (ExecutionException e) { 
      throw new RuntimeException("Error fetching a finished future", e); 
     } 
    } 

    @Override 
    public boolean isDone() { 
     if (!delegate.isDone()) { 
      return false; 
     } 
     try { 
      return delegate.get().isDone(); 
     } catch (InterruptedException e) { 
      throw new RuntimeException("Error fetching a finished future", e); 
     } catch (ExecutionException e) { 
      throw new RuntimeException("Error fetching a finished future", e); 
     } 
    } 
} 
1

Creo que esto es lo mejor que se puede hacer para implementar el contrato de Future. Tomé la táctica de ser lo más imprudente posible para asegurarme de que cumpla con el contrato. No especialmente la implementación de get with timeout.

import java.util.concurrent.*; 

public class Futures { 
    public <T> Future<T> flatten(Future<Future<T>> future) { 
    return new FlattenedFuture<T>(future); 
    } 

    private static class FlattenedFuture<T> implements Future<T> { 
    private final Future<Future<T>> future; 

    public FlattenedFuture(Future<Future<T>> future) { 
     this.future = future; 
    } 

    public boolean cancel(boolean mayInterruptIfRunning) { 
     if (!future.isDone()) { 
     return future.cancel(mayInterruptIfRunning); 
     } else { 
     while (true) { 
      try { 
      return future.get().cancel(mayInterruptIfRunning); 
      } catch (CancellationException ce) { 
      return true; 
      } catch (ExecutionException ee) { 
      return false; 
      } catch (InterruptedException ie) { 
      // pass 
      } 
     } 
     } 
    } 

    public T get() throws InterruptedException, 
          CancellationException, 
          ExecutionException 
    { 
     return future.get().get(); 
    } 

    public T get(long timeout, TimeUnit unit) throws InterruptedException, 
                CancellationException, 
                ExecutionException, 
                TimeoutException 
    { 
     if (future.isDone()) { 
     return future.get().get(timeout, unit); 
     } else { 
     return future.get(timeout, unit).get(0, TimeUnit.SECONDS); 
     } 
    } 

    public boolean isCancelled() { 
     while (true) { 
     try { 
      return future.isCancelled() || future.get().isCancelled(); 
     } catch (CancellationException ce) { 
      return true; 
     } catch (ExecutionException ee) { 
      return false; 
     } catch (InterruptedException ie) { 
      // pass 
     } 
     } 
    } 

    public boolean isDone() { 
     return future.isDone() && innerIsDone(); 
    } 

    private boolean innerIsDone() { 
     while (true) { 
     try { 
      return future.get().isDone(); 
     } catch (CancellationException ce) { 
      return true; 
     } catch (ExecutionException ee) { 
      return true; 
     } catch (InterruptedException ie) { 
      // pass 
     } 
     } 
    } 
    } 
} 
7

Otra posible implementación que utiliza las bibliotecas guava es mucho más simple.

import java.util.concurrent.*; 
import com.google.common.util.concurrent.*; 
import com.google.common.base.*; 

public class FFutures { 
    public <T> Future<T> flatten(Future<Future<T>> future) { 
    return Futures.chain(Futures.makeListenable(future), new Function<Future<T>, ListenableFuture<T>>() { 
     public ListenableFuture<T> apply(Future<T> f) { 
     return Futures.makeListenable(f); 
     } 
    }); 
    } 
} 
+0

Parece que así lo haría y déjame delegar todos los futuros entregas a guayaba. – Nik

Cuestiones relacionadas