Last active
November 27, 2016 09:02
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
mError = e;
should be assigned before setting mState to DONE viaif (mState.compareAndSet(STARTED, DONE))
Will be fixed in next revision