All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bolts.Task Maven / Gradle / Ivy

Go to download

Bolts is a collection of low-level libraries designed to make developing mobile apps easier.

There is a newer version: 1.4.0
Show newest version
/*
 *  Copyright (c) 2014, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant 
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */
package bolts;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Represents the result of an asynchronous operation.
 * 
 * @param 
 *          The type of the result of the task.
 */
public class Task {
  /**
   * An {@link java.util.concurrent.Executor} that executes tasks in parallel.
   */
  public static final ExecutorService BACKGROUND_EXECUTOR = BoltsExecutors.background();

  /**
   * An {@link java.util.concurrent.Executor} that executes tasks in the current thread unless
   * the stack runs too deep, at which point it will delegate to {@link Task#BACKGROUND_EXECUTOR} in
   * order to trim the stack.
   */
  private static final Executor IMMEDIATE_EXECUTOR = BoltsExecutors.immediate();

  /**
   * An {@link java.util.concurrent.Executor} that executes tasks on the UI thread.
   */
  public static final Executor UI_THREAD_EXECUTOR = AndroidExecutors.uiThread();

  private final Object lock = new Object();
  private boolean complete;
  private boolean cancelled;
  private TResult result;
  private Exception error;
  private List> continuations = new ArrayList<>();

  /* package */ Task() {
  }

  private Task(TResult result) {
    trySetResult(result);
  }

  private Task(boolean cancelled) {
    if (cancelled) {
      trySetCancelled();
    } else {
      trySetResult(null);
    }
  }

  /**
   * @deprecated Please use {@link bolts.TaskCompletionSource()} instead.
   */
  public static  Task.TaskCompletionSource create() {
    Task task = new Task<>();
    return task.new TaskCompletionSource();
  }

  /**
   * @return {@code true} if the task completed (has a result, an error, or was cancelled.
   *         {@code false} otherwise.
   */
  public boolean isCompleted() {
    synchronized (lock) {
      return complete;
    }
  }

  /**
   * @return {@code true} if the task was cancelled, {@code false} otherwise.
   */
  public boolean isCancelled() {
    synchronized (lock) {
      return cancelled;
    }
  }

  /**
   * @return {@code true} if the task has an error, {@code false} otherwise.
   */
  public boolean isFaulted() {
    synchronized (lock) {
      return error != null;
    }
  }

  /**
   * @return The result of the task, if set. {@code null} otherwise.
   */
  public TResult getResult() {
    synchronized (lock) {
      return result;
    }
  }

  /**
   * @return The error for the task, if set. {@code null} otherwise.
   */
  public Exception getError() {
    synchronized (lock) {
      return error;
    }
  }

  /**
   * Blocks until the task is complete.
   */
  public void waitForCompletion() throws InterruptedException {
    synchronized (lock) {
      if (!isCompleted()) {
        lock.wait();
      }
    }
  }

  /**
   * Creates a completed task with the given value.
   */
  @SuppressWarnings("unchecked")
  public static  Task forResult(TResult value) {
    if (value == null) {
      return (Task) TASK_NULL;
    }
    if (value instanceof Boolean) {
      return (Task) ((Boolean) value ? TASK_TRUE : TASK_FALSE);
    }
    bolts.TaskCompletionSource tcs = new bolts.TaskCompletionSource<>();
    tcs.setResult(value);
    return tcs.getTask();
  }

  /**
   * Creates a faulted task with the given error.
   */
  public static  Task forError(Exception error) {
    bolts.TaskCompletionSource tcs = new bolts.TaskCompletionSource<>();
    tcs.setError(error);
    return tcs.getTask();
  }

  /**
   * Creates a cancelled task.
   */
  @SuppressWarnings("unchecked")
  public static  Task cancelled() {
    return (Task) TASK_CANCELLED;
  }

  /**
   * Creates a task that completes after a time delay.
   *
   * @param delay The number of milliseconds to wait before completing the returned task. Zero and
   *              negative values are treated as requests for immediate execution.
   */
  public static Task delay(long delay) {
    return delay(delay, BoltsExecutors.scheduled(), null);
  }

  /**
   * Creates a task that completes after a time delay.
   *
   * @param delay The number of milliseconds to wait before completing the returned task. Zero and
   *              negative values are treated as requests for immediate execution.
   * @param cancellationToken The optional cancellation token that will be checked prior to
   *                          completing the returned task.
   */
  public static Task delay(long delay, CancellationToken cancellationToken) {
    return delay(delay, BoltsExecutors.scheduled(), cancellationToken);
  }

  /* package */ static Task delay(long delay, ScheduledExecutorService executor, final CancellationToken cancellationToken) {
    if (cancellationToken != null && cancellationToken.isCancellationRequested()) {
      return Task.cancelled();
    }

    if (delay <= 0) {
      return Task.forResult(null);
    }

    final bolts.TaskCompletionSource tcs = new bolts.TaskCompletionSource<>();
    final ScheduledFuture scheduled = executor.schedule(new Runnable() {
      @Override
      public void run() {
        tcs.trySetResult(null);
      }
    }, delay, TimeUnit.MILLISECONDS);

    if (cancellationToken != null) {
      cancellationToken.register(new Runnable() {
        @Override
        public void run() {
          scheduled.cancel(true);
          tcs.trySetCancelled();
        }
      });
    }

    return tcs.getTask();
  }

  /**
   * Makes a fluent cast of a Task's result possible, avoiding an extra continuation just to cast
   * the type of the result.
   */
  public  Task cast() {
    @SuppressWarnings("unchecked")
    Task task = (Task) this;
    return task;
  }

  /**
   * Turns a Task into a Task, dropping any result.
   */
  public Task makeVoid() {
    return this.continueWithTask(new Continuation>() {
      @Override
      public Task then(Task task) throws Exception {
        if (task.isCancelled()) {
          return Task.cancelled();
        }
        if (task.isFaulted()) {
          return Task.forError(task.getError());
        }
        return Task.forResult(null);
      }
    });
  }

  /**
   * Invokes the callable on a background thread, returning a Task to represent the operation.
   *
   * If you want to cancel the resulting Task throw a {@link java.util.concurrent.CancellationException}
   * from the callable.
   */
  public static  Task callInBackground(Callable callable) {
    return call(callable, BACKGROUND_EXECUTOR, null);
  }

  /**
   * Invokes the callable on a background thread, returning a Task to represent the operation.
   */
  public static  Task callInBackground(Callable callable, CancellationToken ct) {
    return call(callable, BACKGROUND_EXECUTOR, ct);
  }

  /**
   * Invokes the callable using the given executor, returning a Task to represent the operation.
   *
   * If you want to cancel the resulting Task throw a {@link java.util.concurrent.CancellationException}
   * from the callable.
   */
  public static  Task call(final Callable callable, Executor executor) {
    return call(callable, executor, null);
  }

  /**
   * Invokes the callable using the given executor, returning a Task to represent the operation.
   */
  public static  Task call(final Callable callable, Executor executor,
      final CancellationToken ct) {
    final bolts.TaskCompletionSource tcs = new bolts.TaskCompletionSource<>();
    executor.execute(new Runnable() {
      @Override
      public void run() {
        if (ct != null && ct.isCancellationRequested()) {
          tcs.setCancelled();
          return;
        }

        try {
          tcs.setResult(callable.call());
        } catch (CancellationException e) {
          tcs.setCancelled();
        } catch (Exception e) {
          tcs.setError(e);
        }
      }
    });
    return tcs.getTask();
  }

  /**
   * Invokes the callable on the current thread, producing a Task.
   *
   * If you want to cancel the resulting Task throw a {@link java.util.concurrent.CancellationException}
   * from the callable.
   */
  public static  Task call(final Callable callable) {
    return call(callable, IMMEDIATE_EXECUTOR, null);
  }

  /**
   * Invokes the callable on the current thread, producing a Task.
   */
  public static  Task call(final Callable callable, CancellationToken ct) {
    return call(callable, IMMEDIATE_EXECUTOR, ct);
  }

  /**
   * Creates a task that will complete when any of the supplied tasks have completed.
   * 

* The returned task will complete when any of the supplied tasks has completed. The returned task * will always end in the completed state with its result set to the first task to complete. This * is true even if the first task to complete ended in the canceled or faulted state. * * @param tasks * The tasks to wait on for completion. * @return A task that represents the completion of one of the supplied tasks. * The return task's result is the task that completed. */ public static Task> whenAnyResult(Collection> tasks) { if (tasks.size() == 0) { return Task.forResult(null); } final bolts.TaskCompletionSource> firstCompleted = new bolts.TaskCompletionSource<>(); final AtomicBoolean isAnyTaskComplete = new AtomicBoolean(false); for (Task task : tasks) { task.continueWith(new Continuation() { @Override public Void then(Task task) { if (isAnyTaskComplete.compareAndSet(false, true)) { firstCompleted.setResult(task); } return null; } }); } return firstCompleted.getTask(); } /** * Creates a task that will complete when any of the supplied tasks have completed. *

* The returned task will complete when any of the supplied tasks has completed. The returned task * will always end in the completed state with its result set to the first task to complete. This * is true even if the first task to complete ended in the canceled or faulted state. * * @param tasks * The tasks to wait on for completion. * @return A task that represents the completion of one of the supplied tasks. * The return task's Result is the task that completed. */ @SuppressWarnings("unchecked") public static Task> whenAny(Collection> tasks) { if (tasks.size() == 0) { return Task.forResult(null); } final bolts.TaskCompletionSource> firstCompleted = new bolts.TaskCompletionSource<>(); final AtomicBoolean isAnyTaskComplete = new AtomicBoolean(false); for (Task task : tasks) { ((Task) task).continueWith(new Continuation() { @Override public Void then(Task task) { if (isAnyTaskComplete.compareAndSet(false, true)) { firstCompleted.setResult(task); } return null; } }); } return firstCompleted.getTask(); } /** * Creates a task that completes when all of the provided tasks are complete. *

* If any of the supplied tasks completes in a faulted state, the returned task will also complete * in a faulted state, where its exception will resolve to that {@link java.lang.Exception} if a * single task fails or an {@link AggregateException} of all the {@link java.lang.Exception}s * if multiple tasks fail. *

* If none of the supplied tasks faulted but at least one of them was cancelled, the returned * task will end as cancelled. *

* If none of the tasks faulted and none of the tasks were cancelled, the resulting task will end * completed. The result of the returned task will be set to a list containing all of the results * of the supplied tasks in the same order as they were provided (e.g. if the input tasks collection * contained t1, t2, t3, the output task's result will return an {@code List<TResult>} * where {@code list.get(0) == t1.getResult(), list.get(1) == t2.getResult(), and * list.get(2) == t3.getResult()}). *

* If the supplied collection contains no tasks, the returned task will immediately transition to * a completed state before it's returned to the caller. * The returned {@code List<TResult>} will contain 0 elements. * * @param tasks The tasks that the return value will wait for before completing. * @return A Task that will resolve to {@code List<TResult>} when all the tasks are resolved. */ public static Task> whenAllResult(final Collection> tasks) { return whenAll(tasks).onSuccess(new Continuation>() { @Override public List then(Task task) throws Exception { if (tasks.size() == 0) { return Collections.emptyList(); } List results = new ArrayList<>(); for (Task individualTask : tasks) { results.add(individualTask.getResult()); } return results; } }); } /** * Creates a task that completes when all of the provided tasks are complete. *

* If any of the supplied tasks completes in a faulted state, the returned task will also complete * in a faulted state, where its exception will resolve to that {@link java.lang.Exception} if a * single task fails or an {@link AggregateException} of all the {@link java.lang.Exception}s * if multiple tasks fail. *

* If none of the supplied tasks faulted but at least one of them was cancelled, the returned * task will end as cancelled. *

* If none of the tasks faulted and none of the tasks were canceled, the resulting task will * end in the completed state. *

* If the supplied collection contains no tasks, the returned task will immediately transition * to a completed state before it's returned to the caller. * * @param tasks The tasks that the return value will wait for before completing. * @return A Task that will resolve to {@code Void} when all the tasks are resolved. */ public static Task whenAll(Collection> tasks) { if (tasks.size() == 0) { return Task.forResult(null); } final bolts.TaskCompletionSource allFinished = new bolts.TaskCompletionSource<>(); final ArrayList causes = new ArrayList<>(); final Object errorLock = new Object(); final AtomicInteger count = new AtomicInteger(tasks.size()); final AtomicBoolean isCancelled = new AtomicBoolean(false); for (Task task : tasks) { @SuppressWarnings("unchecked") Task t = (Task) task; t.continueWith(new Continuation() { @Override public Void then(Task task) { if (task.isFaulted()) { synchronized (errorLock) { causes.add(task.getError()); } } if (task.isCancelled()) { isCancelled.set(true); } if (count.decrementAndGet() == 0) { if (causes.size() != 0) { if (causes.size() == 1) { allFinished.setError(causes.get(0)); } else { Exception error = new AggregateException( String.format("There were %d exceptions.", causes.size()), causes); allFinished.setError(error); } } else if (isCancelled.get()) { allFinished.setCancelled(); } else { allFinished.setResult(null); } } return null; } }); } return allFinished.getTask(); } /** * Continues a task with the equivalent of a Task-based while loop, where the body of the loop is * a task continuation. */ public Task continueWhile(Callable predicate, Continuation> continuation) { return continueWhile(predicate, continuation, IMMEDIATE_EXECUTOR, null); } /** * Continues a task with the equivalent of a Task-based while loop, where the body of the loop is * a task continuation. */ public Task continueWhile(Callable predicate, Continuation> continuation, CancellationToken ct) { return continueWhile(predicate, continuation, IMMEDIATE_EXECUTOR, ct); } /** * Continues a task with the equivalent of a Task-based while loop, where the body of the loop is * a task continuation. */ public Task continueWhile(final Callable predicate, final Continuation> continuation, final Executor executor) { return continueWhile(predicate, continuation, executor, null); } /** * Continues a task with the equivalent of a Task-based while loop, where the body of the loop is * a task continuation. */ public Task continueWhile(final Callable predicate, final Continuation> continuation, final Executor executor, final CancellationToken ct) { final Capture>> predicateContinuation = new Capture<>(); predicateContinuation.set(new Continuation>() { @Override public Task then(Task task) throws Exception { if (ct != null && ct.isCancellationRequested()) { return Task.cancelled(); } if (predicate.call()) { return Task. forResult(null).onSuccessTask(continuation, executor) .onSuccessTask(predicateContinuation.get(), executor); } return Task.forResult(null); } }); return makeVoid().continueWithTask(predicateContinuation.get(), executor); } /** * Adds a continuation that will be scheduled using the executor, returning a new task that * completes after the continuation has finished running. This allows the continuation to be * scheduled on different thread. */ public Task continueWith( final Continuation continuation, final Executor executor) { return continueWith(continuation, executor, null); } /** * Adds a continuation that will be scheduled using the executor, returning a new task that * completes after the continuation has finished running. This allows the continuation to be * scheduled on different thread. */ public Task continueWith( final Continuation continuation, final Executor executor, final CancellationToken ct) { boolean completed; final bolts.TaskCompletionSource tcs = new bolts.TaskCompletionSource<>(); synchronized (lock) { completed = this.isCompleted(); if (!completed) { this.continuations.add(new Continuation() { @Override public Void then(Task task) { completeImmediately(tcs, continuation, task, executor, ct); return null; } }); } } if (completed) { completeImmediately(tcs, continuation, this, executor, ct); } return tcs.getTask(); } /** * Adds a synchronous continuation to this task, returning a new task that completes after the * continuation has finished running. */ public Task continueWith( Continuation continuation) { return continueWith(continuation, IMMEDIATE_EXECUTOR, null); } /** * Adds a synchronous continuation to this task, returning a new task that completes after the * continuation has finished running. */ public Task continueWith( Continuation continuation, CancellationToken ct) { return continueWith(continuation, IMMEDIATE_EXECUTOR, ct); } /** * Adds an Task-based continuation to this task that will be scheduled using the executor, * returning a new task that completes after the task returned by the continuation has completed. */ public Task continueWithTask( final Continuation> continuation, final Executor executor) { return continueWithTask(continuation, executor, null); } /** * Adds an Task-based continuation to this task that will be scheduled using the executor, * returning a new task that completes after the task returned by the continuation has completed. */ public Task continueWithTask( final Continuation> continuation, final Executor executor, final CancellationToken ct) { boolean completed; final bolts.TaskCompletionSource tcs = new bolts.TaskCompletionSource<>(); synchronized (lock) { completed = this.isCompleted(); if (!completed) { this.continuations.add(new Continuation() { @Override public Void then(Task task) { completeAfterTask(tcs, continuation, task, executor, ct); return null; } }); } } if (completed) { completeAfterTask(tcs, continuation, this, executor, ct); } return tcs.getTask(); } /** * Adds an asynchronous continuation to this task, returning a new task that completes after the * task returned by the continuation has completed. */ public Task continueWithTask( Continuation> continuation) { return continueWithTask(continuation, IMMEDIATE_EXECUTOR, null); } /** * Adds an asynchronous continuation to this task, returning a new task that completes after the * task returned by the continuation has completed. */ public Task continueWithTask( Continuation> continuation, CancellationToken ct) { return continueWithTask(continuation, IMMEDIATE_EXECUTOR, ct); } /** * Runs a continuation when a task completes successfully, forwarding along * {@link java.lang.Exception} or cancellation. */ public Task onSuccess( final Continuation continuation, Executor executor) { return onSuccess(continuation, executor, null); } /** * Runs a continuation when a task completes successfully, forwarding along * {@link java.lang.Exception} or cancellation. */ public Task onSuccess( final Continuation continuation, Executor executor, final CancellationToken ct) { return continueWithTask(new Continuation>() { @Override public Task then(Task task) { if (ct != null && ct.isCancellationRequested()) { return Task.cancelled(); } if (task.isFaulted()) { return Task.forError(task.getError()); } else if (task.isCancelled()) { return Task.cancelled(); } else { return task.continueWith(continuation); } } }, executor); } /** * Runs a continuation when a task completes successfully, forwarding along * {@link java.lang.Exception}s or cancellation. */ public Task onSuccess( final Continuation continuation) { return onSuccess(continuation, IMMEDIATE_EXECUTOR, null); } /** * Runs a continuation when a task completes successfully, forwarding along * {@link java.lang.Exception}s or cancellation. */ public Task onSuccess( final Continuation continuation, CancellationToken ct) { return onSuccess(continuation, IMMEDIATE_EXECUTOR, ct); } /** * Runs a continuation when a task completes successfully, forwarding along * {@link java.lang.Exception}s or cancellation. */ public Task onSuccessTask( final Continuation> continuation, Executor executor) { return onSuccessTask(continuation, executor, null); } /** * Runs a continuation when a task completes successfully, forwarding along * {@link java.lang.Exception}s or cancellation. */ public Task onSuccessTask( final Continuation> continuation, Executor executor, final CancellationToken ct) { return continueWithTask(new Continuation>() { @Override public Task then(Task task) { if (ct != null && ct.isCancellationRequested()) { return Task.cancelled(); } if (task.isFaulted()) { return Task.forError(task.getError()); } else if (task.isCancelled()) { return Task.cancelled(); } else { return task.continueWithTask(continuation); } } }, executor); } /** * Runs a continuation when a task completes successfully, forwarding along * {@link java.lang.Exception}s or cancellation. */ public Task onSuccessTask( final Continuation> continuation) { return onSuccessTask(continuation, IMMEDIATE_EXECUTOR); } /** * Runs a continuation when a task completes successfully, forwarding along * {@link java.lang.Exception}s or cancellation. */ public Task onSuccessTask( final Continuation> continuation, CancellationToken ct) { return onSuccessTask(continuation, IMMEDIATE_EXECUTOR, ct); } /** * Handles the non-async (i.e. the continuation doesn't return a Task) continuation case, passing * the results of the given Task through to the given continuation and using the results of that * call to set the result of the TaskContinuationSource. * * @param tcs * The TaskContinuationSource that will be orchestrated by this call. * @param continuation * The non-async continuation. * @param task * The task being completed. * @param executor * The executor to use when running the continuation (allowing the continuation to be * scheduled on a different thread). */ private static void completeImmediately( final bolts.TaskCompletionSource tcs, final Continuation continuation, final Task task, Executor executor, final CancellationToken ct) { executor.execute(new Runnable() { @Override public void run() { if (ct != null && ct.isCancellationRequested()) { tcs.setCancelled(); return; } try { TContinuationResult result = continuation.then(task); tcs.setResult(result); } catch (CancellationException e) { tcs.setCancelled(); } catch (Exception e) { tcs.setError(e); } } }); } /** * Handles the async (i.e. the continuation does return a Task) continuation case, passing the * results of the given Task through to the given continuation to get a new Task. The * TaskCompletionSource's results are only set when the new Task has completed, unwrapping the * results of the task returned by the continuation. * * @param tcs * The TaskContinuationSource that will be orchestrated by this call. * @param continuation * The async continuation. * @param task * The task being completed. * @param executor * The executor to use when running the continuation (allowing the continuation to be * scheduled on a different thread). */ private static void completeAfterTask( final bolts.TaskCompletionSource tcs, final Continuation> continuation, final Task task, final Executor executor, final CancellationToken ct) { executor.execute(new Runnable() { @Override public void run() { if (ct != null && ct.isCancellationRequested()) { tcs.setCancelled(); return; } try { Task result = continuation.then(task); if (result == null) { tcs.setResult(null); } else { result.continueWith(new Continuation() { @Override public Void then(Task task) { if (ct != null && ct.isCancellationRequested()) { tcs.setCancelled(); return null; } if (task.isCancelled()) { tcs.setCancelled(); } else if (task.isFaulted()) { tcs.setError(task.getError()); } else { tcs.setResult(task.getResult()); } return null; } }); } } catch (CancellationException e) { tcs.setCancelled(); } catch (Exception e) { tcs.setError(e); } } }); } private void runContinuations() { synchronized (lock) { for (Continuation continuation : continuations) { try { continuation.then(this); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } continuations = null; } } /** * Sets the cancelled flag on the Task if the Task hasn't already been completed. */ /* package */ boolean trySetCancelled() { synchronized (lock) { if (complete) { return false; } complete = true; cancelled = true; lock.notifyAll(); runContinuations(); return true; } } /** * Sets the result on the Task if the Task hasn't already been completed. */ /* package */ boolean trySetResult(TResult result) { synchronized (lock) { if (complete) { return false; } complete = true; Task.this.result = result; lock.notifyAll(); runContinuations(); return true; } } /** * Sets the error on the Task if the Task hasn't already been completed. */ /* package */ boolean trySetError(Exception error) { synchronized (lock) { if (complete) { return false; } complete = true; Task.this.error = error; lock.notifyAll(); runContinuations(); return true; } } /** * @deprecated Please use {@link bolts.TaskCompletionSource} instead. */ public class TaskCompletionSource extends bolts.TaskCompletionSource { /* package */ TaskCompletionSource() { } } private static Task TASK_NULL = new Task<>(null); private static Task TASK_TRUE = new Task<>((Boolean) true); private static Task TASK_FALSE = new Task<>((Boolean) false); private static Task TASK_CANCELLED = new Task(true); }