Skip to content

Instantly share code, notes, and snippets.

@mrleolink
Last active November 27, 2016 09:02
Show Gist options
  • Save mrleolink/24245ad2698a3b087a77514ed427ae72 to your computer and use it in GitHub Desktop.
Save mrleolink/24245ad2698a3b087a77514ed427ae72 to your computer and use it in GitHub Desktop.
An implementation of Future which also provides UI Thread callback for Android - Revision 1 is reviewed here: http://codereview.stackexchange.com/questions/143608/combination-of-javas-future-and-androids-asynctask
import android.os.Handler;
import android.os.Looper;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Leo
*/
public class AsyncFuture<T> implements Future<T>, Runnable {
// Possible patterns:
// NOT_STARTED -> STARTED -> DONE
// NOT_STARTED -> STARTED -> CANCELLED
// NOT_STARTED -> CANCELLED
public static final int NOT_STARTED = 0;
public static final int STARTED = 1;
public static final int DONE = 2;
public static final int CANCELLED = 3;
private static final ExecutorService mExecutor = Executors.newCachedThreadPool(); // could be improved
private static final Handler mHandler = new Handler(Looper.getMainLooper());
private Thread mRunnerThread;
private Callable<T> mCallable = null;
private CountDownLatch mLatch = null;
private Listener<T> mListener = null;
private AtomicInteger mState = new AtomicInteger(NOT_STARTED);
private Throwable mError = null;
private T mResult = null;
public AsyncFuture(Callable<T> callable) {
this(callable, null);
}
public AsyncFuture(Callable<T> callable, Listener listener) {
this.mCallable = callable;
this.mLatch = new CountDownLatch(1);
this.mListener = listener;
}
public AsyncFuture start() {
mExecutor.execute(this);
return this;
}
@Override
public void run() {
// save runner thread before changing state to STARTED
mRunnerThread = Thread.currentThread();
// atomically change state to STARTED
if (!mState.compareAndSet(NOT_STARTED, STARTED)) {
return;
}
try {
// actually run the time-consuming job
mResult = mCallable.call();
// decide whether to post result to the mListener
if (mState.compareAndSet(STARTED, DONE)) {
if (mListener != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
mListener.onSuccess(mResult);
}
});
}
}
} catch (final Exception e) {
// save error
mError = e;
// decide whether to post error to the mListener
if (mState.compareAndSet(STARTED, DONE)) {
if (mListener != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
mListener.onError(e);
}
});
}
}
} finally {
// let the dog out get() get()
mLatch.countDown();
}
}
/**
* Convenient method for calling {@link #cancel(false)}
*/
public boolean cancel() {
return cancel(false);
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if (mayInterruptIfRunning && mRunnerThread != null) mRunnerThread.interrupt(); // signal mRunnerThread about the interuption
if (mState.compareAndSet(NOT_STARTED, CANCELLED) || mState.compareAndSet(STARTED, CANCELLED)) {
return true;
}
return false;
}
@Override
public boolean isCancelled() {
return mState.get() == CANCELLED;
}
@Override
public boolean isDone() {
return mState.get() == DONE || mState.get() == CANCELLED;
}
@Override
public T get() throws InterruptedException, ExecutionException {
if (mState.get() == NOT_STARTED || mState.get() == STARTED) {
mLatch.await();
}
if (mState.get() == CANCELLED) {
throw new InterruptedException("Cancelled!");
} else if (mError != null) {
throw new ExecutionException(mError);
} else {
return mResult;
}
}
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
boolean timedOut = false;
if (mState.get() == NOT_STARTED || mState.get() == STARTED) {
timedOut = !mLatch.await(timeout, unit);
}
if (timedOut) {
throw new InterruptedException("Timed out!");
} else if (mState.get() == CANCELLED) {
throw new InterruptedException("Cancelled!");
} else if (mError != null) {
throw new ExecutionException(mError);
} else {
return mResult;
}
}
// listener interface
public interface Listener<T> {
void onSuccess(T res);
void onError(Throwable err);
}
}
@mrleolink
Copy link
Author

mError = e; should be assigned before setting mState to DONE via if (mState.compareAndSet(STARTED, DONE))

Will be fixed in next revision

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment