com.ajjpj.afoundation.concurrent.AFuture Maven / Gradle / Ivy
package com.ajjpj.afoundation.concurrent;
import com.ajjpj.afoundation.collection.tuples.ATuple2;
import com.ajjpj.afoundation.collection.tuples.ATuple3;
import com.ajjpj.afoundation.function.AFunction1;
import com.ajjpj.afoundation.function.AStatement1NoThrow;
import com.ajjpj.afoundation.function.AStatement2NoThrow;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* An instance of AFuture
represents a result that may or may not be available yet. Its methods can be
* categorized as follows:
*
* - Interaction with the underlying computation. A call to {@link #cancel(boolean)} cancels that computation unless it is already finished, while {@link #isCancelled()}
* and {@link #isFinished()} check if the computation was cancelled or finished in whatever way, respectively.
*
- Blocking access to the result. Calling {@link #get()} with or without a timeout will return the result, blocking until it becomes available if necessary. This is
* pretty crude, and it wastes resources by blocking a thread. However it makes for straightforward code - but you should consider registering a callback instead.
*
- Registering callbacks. Calling {@link #onSuccess(com.ajjpj.afoundation.function.AStatement1NoThrow)}, {@link #onFailure(com.ajjpj.afoundation.function.AStatement1NoThrow)} or
* {@link #onFinished(com.ajjpj.afoundation.function.AStatement2NoThrow)} registers a callback that is triggered when the future completes successfully, fails (i.e. the task
* threw an exception or timed out}, or finished either way. Using these callbacks is the preferred way of dealing with results because it does not block any threads.
* Callbacks are guaranteed to be executed even if they are registered after the future's computation is finished.
*
- Creating a new
AFuture
base on this future's result without waiting for it to finish first. You can provide a default value in case this future's
* computation fails or times out ({@link #withDefaultValue(Object)}), or you can transform the result {@link #mapSync(com.ajjpj.afoundation.function.AFunction1) synchronously}
* or {@link #mapAsync(com.ajjpj.afoundation.function.AFunction1, long, java.util.concurrent.TimeUnit) asynchronously}. You can also call one of the zip
methods
* to combine {@link #zip(AFuture) two} or {@link #zip(AFuture, AFuture) more} AFuture
s into one.
* This way of working with futures as if they were actual values, but without blocking any threads to wait for intermediate results is one of the key benefits of
* using {@link AThreadPool} and AFuture
. {@link AFutureHelper} contains more methods for working with AFuture
instances.
*
*
* @author arno
*/
public interface AFuture {
/**
* This method cancels the underlying computation unless it is finished already. If the task is not yet running, this method ensures that it never will.
* @param mayInterruptIfRunning determines whether the underlying thread pool should attempt to interrupt the worker thread if the underlying task is currently running.
*/
boolean cancel (boolean mayInterruptIfRunning);
/**
* @return true if and only if this future was cancelled.
*/
boolean isCancelled ();
/**
* @return true if and only if this future's task is finished, be it through cancellation, success, failure or timeout.
*/
boolean isFinished ();
/**
* @return a new AFuture
with a lifecycle that is bound to this AFuture
's. The only difference is that if this future fails, the returned
* future will instead finish successfully with the provided defaultValue
.
*/
AFuture withDefaultValue (T defaultValue);
/**
* This method returns the future's result, performing a blocking wait until it becomes available. While this waiting looks like it is unbounded, it is actually bounded
* by the underlying task's timeout. Nonetheless, this method should be used with extreme care because it can block the caller thread for a potentially very long time.
*
* @throws java.lang.InterruptedException if the underlying task was interrupted
* @throws java.util.concurrent.ExecutionException if the underlying task failed. The actual exception causing the failure is wrapped in the ExecutionException
.
*/
T get () throws InterruptedException, ExecutionException;
/**
* This method returns the future's result, performing a blocking wait until it becomes available or this method's timeout is reached. This method should be used with extreme
* care because it can block the caller thread.
*
* @throws java.lang.InterruptedException if the underlying task was interrupted
* @throws java.util.concurrent.ExecutionException if the underlying task failed. The actual exception causing the failure is wrapped in the ExecutionException
.
* @throws java.util.concurrent.TimeoutException if this method's timeout was reached
*/
T get(long timeout, TimeUnit timeoutUnit) throws InterruptedException, ExecutionException, TimeoutException;
/**
* Registers a callback that is called if and when the future completes successfully.
*
* It is permissible to register several callbacks, in which case they are all called sequentially. Callbacks are guaranteed to be called, even if they are registered
* after a future finished.
*
* Callbacks are called in a 'borrowed' thread, i.e. either the task's thread (if they are registered before the task is finished) or the caller's thread (if they are
* registered after the task is finished). They should typically do only safe and fast work in place, submitting anything else as separate tasks.
*/
void onSuccess (AStatement1NoThrow callback);
/**
* Registers a callback that is called if and when the future fails, i.e. the task throws an exception or times out. The callback receives the cause of failure as a
* parameter.
*
* It is permissible to register several callbacks, in which case they are all called sequentially. Callbacks are guaranteed to be called, even if they are registered
* after a future finished.
*
* Callbacks are called in a 'borrowed' thread, i.e. either the task's thread (if they are registered before the task is finished) or the caller's thread (if they are
* registered after the task is finished). They should typically do only safe and fast work in place, submitting anything else as separate tasks.
*/
void onFailure (AStatement1NoThrow callback);
/**
* Registers a callback that is called when a future finishes - either successfully or by failing. On success, the callback receives the future's value as its first parameter
* while the second parameter is null
. On failure, the first paramter is null
while the second paramter contains the cause of failure. The
* canonical way to distinguish between the two is to compare the second parameter with null
.
*
* It is permissible to register several callbacks, in which case they are all called sequentially. Callbacks are guaranteed to be called, even if they are registered
* after a future finished.
*
* Callbacks are called in a 'borrowed' thread, i.e. either the task's thread (if they are registered before the task is finished) or the caller's thread (if they are
* registered after the task is finished). They should typically do only safe and fast work in place, submitting anything else as separate tasks.
*/
void onFinished (AStatement2NoThrow callback);
/**
* Creates a new AFuture
instance with a lifecycle that is bound to this AFuture
's lifecycle, applying a given function f
to the result.
* This method does for futures what {@link com.ajjpj.afoundation.collection.immutable.ACollection#map(com.ajjpj.afoundation.function.AFunction1)} does for collecctions.
*
* This method causes the transformation function to be applied in the original future's thread and is thus outside of timeout checks. For fast operations, this saves
* scheduling overhead. For non-trivial transformations, use {@link #mapAsync(com.ajjpj.afoundation.function.AFunction1, long, java.util.concurrent.TimeUnit) mapAsync} instead.
*/
AFuture mapSync (AFunction1 f);
/**
* Creates a new AFuture
. If this future fails, the newly created future fails as well. If this future completes successfully, a new task is scheduled, applying
* the transformation function to this future's result. This method does for futures what
* {@link com.ajjpj.afoundation.collection.immutable.ACollection#map(com.ajjpj.afoundation.function.AFunction1)} does for collecctions.
*
* Scheduling is done in this future's underlying thread pool, using the timeout parameter.
*/
AFuture mapAsync (AFunction1 f, long timeout, TimeUnit timeoutUnit);
/**
* Creates a new AFuture
. If this future fails, the newly created future fails as well. If this future completes successfully, a new task is scheduled, applying
* the transformation function to this future's result. This method does for futures what
* {@link com.ajjpj.afoundation.collection.immutable.ACollection#map(com.ajjpj.afoundation.function.AFunction1)} does for collecctions.
*
* Scheduling is done in the explicitly provided thread pool, using the timeout parameter.
*/
AFuture mapAsync (AFunction1 f, ATaskScheduler threadPool, long timeout, TimeUnit timeoutUnit);
/**
* Combines this AFuture
with another AFuture
into a single future with an {@link com.ajjpj.afoundation.collection.tuples.ATuple2} of the two values. If either
* of the original futures fails, the newly created future fails. If both futures succeed, the newly created future succeeds with the combined value.
*/
AFuture> zip (AFuture other);
/**
* Combines this AFuture
with two other AFuture
s into a single future with an {@link com.ajjpj.afoundation.collection.tuples.ATuple3} of the three values.
* If one of the original futures fails, the newly created future fails. If all three futures succeed, the newly created future succeeds with the combined value.
*
* If you want to combine more than three futures into a single future, use {@link AFutureHelper#lift(java.util.Collection)}.
*/
AFuture> zip (AFuture other1, AFuture other2);
}