Bolts框架中Task的用法

2017-02-13  本文已影响0人  哦嘿嘿哈哈吼

项目地址:Bolts-Android

Task是为了更好的书写复杂异步操作而设计的,运用了Javascript的Promise思想。

如果想要构建一个响应迅速的Android应用,那么你就不能在UI线程中运行任何耗时操作,避免阻塞UI线程,这也就意味着你需要在后台中执行大量的操作。为了让这一过程变得更简单,我们增加了这个叫做Task的类。一个Task代表一个异步操作。通常情况下,我们会写一个方法返回一个Task,这个Task具有继续操作任务结果的能力。当这个Task被方法返回时,它已经开始执行它的任务了。Task不与特定的线程模型进行绑定:它代表要被完成的操作,而不是执行操作的地点。Task与其他异步方法(CallbacksAsyncTask)相比有许多优势。

continueWith方法

每个Task都有一个continueWith方法,带有一个Continuation参数。Continuation是一个接口,你可以实现它的then方法,then方法会在任务完成的时候调用,你可以在这里检查任务的完成状态并做相应的处理。

saveAsync(obj).continueWith(new Continuation<ParseObject, Void>() {
  public Void then(Task<ParseObject> task) throws Exception {
    if (task.isCancelled()) {
      // the save was cancelled.
    } else if (task.isFaulted()) {
      // the save failed.
      Exception error = task.getError();
    } else {
      // the object was saved successfully.
      ParseObject object = task.getResult();
    }
    return null;
  }
});

Tasks使用了强类型的Java泛型,所有一开始就想要书写语法正确的代码可能需要一点点技巧。下面通过一个例子深入了解一下。

/**
 Gets a String asynchronously.
 */
public Task<String> getStringAsync() {
  // Let's suppose getIntAsync() returns a Task<Integer>.
  return getIntAsync().continueWith(
    // This Continuation is a function which takes an Integer as input,
    // and provides a String as output. It must take an Integer because
    // that's what was returned from the previous Task.
    new Continuation<Integer, String>() {
      // The Task getIntAsync() returned is passed to "then" for convenience.
      public String then(Task<Integer> task) throws Exception {
        Integer number = task.getResult();
        return String.format("%d", Locale.US, number);
      }
    }
  );
}

在许多情况下,你可能只是想在前一个任务成功结束时做一点微小的工作,并把发生错误和任务取消的情况留到以后处理,那么你可以使用onSuccess方法代替continueWith方法。

saveAsync(obj).onSuccess(new Continuation<ParseObject, Void>() {
  public Void then(Task<ParseObject> task) throws Exception {
    // the object was saved successfully.
    return null;
  }
});

Task链式编程

我们对Task做了一些膜法,支持链式调用而不用编写复杂的嵌套逻辑。你可以使用continueWithTask来代替continueWith,它会返回一个新的Task。由continueWithTask返回的Task在新的任务执行结束之前不会被认为结束。另外,onSuccessTask是能够返回新Task版的onSuccess,你可以使用onSuccess/continueWith来执行更多的同步操作,或者使用onSuccessTask/continueWithTask来执行更多的异步操作。

final ParseQuery<ParseObject> query = ParseQuery.getQuery("Student");
query.orderByDescending("gpa");
findAsync(query).onSuccessTask(new Continuation<List<ParseObject>, Task<ParseObject>>() {
  public Task<ParseObject> then(Task<List<ParseObject>> task) throws Exception {
    List<ParseObject> students = task.getResult();
    students.get(0).put("valedictorian", true);
    return saveAsync(students.get(0));
  }
}).onSuccessTask(new Continuation<ParseObject, Task<List<ParseObject>>>() {
  public Task<List<ParseObject>> then(Task<ParseObject> task) throws Exception {
    ParseObject valedictorian = task.getResult();
    return findAsync(query);
  }
}).onSuccessTask(new Continuation<List<ParseObject>, Task<ParseObject>>() {
  public Task<ParseObject> then(Task<List<ParseObject>> task) throws Exception {
    List<ParseObject> students = task.getResult();
    students.get(1).put("salutatorian", true);
    return saveAsync(students.get(1));
  }
}).onSuccess(new Continuation<ParseObject, Void>() {
  public Void then(Task<ParseObject> task) throws Exception {
    // Everything is done!
    return null;
  }
});

异常处理

在书写你的应用时,谨慎的选择调用continueWithonSuccess可以帮助你控制异常的传递方式。使用continueWith可以传递发生的异常或对它进行某些处理。你可以考虑用抛出异常的方式使一个Task失败,事实上,如果你在continuation中抛出了一个异常,Task的结果会显示失败并返回这个异常。

final ParseQuery<ParseObject> query = ParseQuery.getQuery("Student");
query.orderByDescending("gpa");
findAsync(query).onSuccessTask(new Continuation<List<ParseObject>, Task<ParseObject>>() {
  public Task<ParseObject> then(Task<List<ParseObject>> task) throws Exception {
    List<ParseObject> students = task.getResult();
    students.get(0).put("valedictorian", true);
    // Force this callback to fail.
    throw new RuntimeException("There was an error.");
  }
}).onSuccessTask(new Continuation<ParseObject, Task<List<ParseObject>>>() {
  public Task<List<ParseObject>> then(Task<ParseObject> task) throws Exception {
    // Now this continuation will be skipped.
    ParseObject valedictorian = task.getResult();
    return findAsync(query);
  }
}).continueWithTask(new Continuation<List<ParseObject>, Task<ParseObject>>() {
  public Task<ParseObject> then(Task<List<ParseObject>> task) throws Exception {
    if (task.isFaulted()) {
      // This error handler WILL be called.
      // The exception will be "There was an error."
      // Let's handle the error by returning a new value.
      // The task will be completed with null as its value.
      return null;
    }

    // This will also be skipped.
    List<ParseObject> students = task.getResult();
    students.get(1).put("salutatorian", true);
    return saveAsync(students.get(1));
  }
}).onSuccess(new Continuation<ParseObject, Void>() {
  public Void then(Task<ParseObject> task) throws Exception {
    // Everything is done! This gets called.
    // The task's result is null.
    return null;
  }
});

这有利于书写很长的只处理成功情况的链式调用,只需要在调用链末尾书写一个错误处理即可。

创建Task

一开始,你可能只是使用类似findAsyncsaveAsync这种方法返回的简单的Task。但是,对于更高级的方案,你可能想创建自定义的Task。为了实现这个需求,你创建了一个TaskCompletionSource。这个对象允许你创建新的Task并且控制它的执行结果是已完成或取消。在你创建了一个Task之后,你需要调用setResultseterrorsetCancelled来触发它之后的操作。

public Task<String> succeedAsync() {
  TaskCompletionSource<String> successful = new TaskCompletionSource<>();
  successful.setResult("The good result.");
  return successful.getTask();
}

public Task<String> failAsync() {
  TaskCompletionSource<String> failed = new TaskCompletionSource<>();
  failed.setError(new RuntimeException("An error message."));
  return failed.getTask();
}

如果你在一个Task创建时就知道他某个结果需要执行的操作,你可以使用下面这些比较方便的方法。

Task<String> successful = Task.forResult("The good result.");

Task<String> failed = Task.forError(new RuntimeException("An error message."));

创建异步方法

使用如下方法,你可以很轻松的创建你自己的异步任务并返回一个Task

public Task<ParseObject> fetchAsync(ParseObject obj) {
  final TaskCompletionSource<ParseObject> tcs = new TaskCompletionSource<>();
  obj.fetchInBackground(new GetCallback() {
    public void done(ParseObject object, ParseException e) {
     if (e == null) {
       tcs.setResult(object);
     } else {
       tcs.setError(e);
     }
   }
  });
  return tcs.getTask();
}

我们同样提供了方法方便你在代码块中创建Task。当执行到call代码块时,callInBackground会在后台线程池中执行Task

Task.callInBackground(new Callable<Void>() {
  public Void call() {
    // Do a bunch of stuff.
  }
}).continueWith(...);

顺次执行Task

Task允许你执行一连串的异步任务,每个任务都会在前一个任务完成后再执行。举个例子,你想要删除你博客上的所有评论。

ParseQuery<ParseObject> query = ParseQuery.getQuery("Comments");
query.whereEqualTo("post", 123);

findAsync(query).continueWithTask(new Continuation<List<ParseObject>, Task<Void>>() {
  public Task<Void> then(Task<List<ParseObject>> results) throws Exception {
    // Create a trivial completed task as a base case.
    Task<Void> task = Task.forResult(null);
    for (final ParseObject result : results) {
      // For each item, extend the task with a function to delete the item.
      task = task.continueWithTask(new Continuation<Void, Task<Void>>() {
        public Task<Void> then(Task<Void> ignored) throws Exception {
          // Return a task that will be marked as completed when the delete is finished.
          return deleteAsync(result);
        }
      });
    }
    return task;
  }
}).continueWith(new Continuation<Void, Void>() {
  public Void then(Task<Void> ignored) throws Exception {
    // Every comment was deleted.
    return null;
  }
});

同时执行多个Task

你可以调用whenall方法来同步执行多个TaskTask.whenall会创建一个新的Task,此Task会在输入的所有Task都执行完毕后再标记为完成状态,此Task只会在所有传入Task都成功时标记为成功状态。同时执行任务比顺次执行任务更快,但可能消耗更多的系统资源和带宽。

ParseQuery<ParseObject> query = ParseQuery.getQuery("Comments");
query.whereEqualTo("post", 123);

findAsync(query).continueWithTask(new Continuation<List<ParseObject>, Task<Void>>() {
  public Task<Void> then(Task<List<ParseObject>> results) throws Exception {
    // Collect one task for each delete into an array.
    ArrayList<Task<Void>> tasks = new ArrayList<Task<Void>>();
    for (ParseObject result : results) {
      // Start this delete immediately and add its task to the list.
      tasks.add(deleteAsync(result));
    }
    // Return a new task that will be marked as completed when all of the deletes are
    // finished.
    return Task.whenAll(tasks);
  }
}).onSuccess(new Continuation<Void, Void>() {
  public Void then(Task<Void> ignored) throws Exception {
    // Every comment was deleted.
    return null;
  }
});

Task Executors

所有continueWithonSuccess方法都可将java.util.concurrent.Executor的实例作为参数传入。这让你可以控制后续任务在哪里执行。默认状态下,Task.call()会在当前线程执行CallableTask.callInBackgorund会在自己的线程池中执行,你也可以提供自己的Executor在其它线程中执行任务。举个例子,假如你想要在一个特别的线程池中执行任务。

static final Executor NETWORK_EXECUTOR = Executors.newCachedThreadPool();
static final Executor DISK_EXECUTOR = Executors.newCachedThreadPool();
final Request request = ...
Task.call(new Callable<HttpResponse>() {
  @Override
  public HttpResponse call() throws Exception {
    // Work is specified to be done on NETWORK_EXECUTOR
    return client.execute(request);
  }
}, NETWORK_EXECUTOR).continueWithTask(new Continuation<HttpResponse, Task<byte[]>>() {
  @Override
  public Task<byte[]> then(Task<HttpResponse> task) throws Exception {
    // Since no executor is specified, it's continued on NETWORK_EXECUTOR
    return processResponseAsync(response);
  }
}).continueWithTask(new Continuation<byte[], Task<Void>>() {
  @Override
  public Task<Void> then(Task<byte[]> task) throws Exception {
    // We don't want to clog NETWORK_EXECUTOR with disk I/O, so we specify to use DISK_EXECUTOR
    return writeToDiskAsync(task.getResult());
  }
}, DISK_EXECUTOR);

对于常用场景,例如分发至主线程执行,我们提供了默认的实现:Task.UI_THREAD_EXECUTORTask.BACKGROUND_EXECUTOR

fetchAsync(object).continueWith(new Continuation<ParseObject, Void>() {
  public Void then(Task<ParseObject> object) throws Exception {
    TextView textView = (TextView)findViewById(R.id.name);
    textView.setText(object.get("name"));
    return null;
  }
}, Task.UI_THREAD_EXECUTOR);

捕获变量

将代码重构为多个回调的难点在于他们具有不同的变量作用域。Java允许你使用外部域的变量,但前提是他必须被声明为final,这非常的不方便,因为会导致这些变量不可变。这也是我们添加Capture这个类的原因,它允许你在各个回调之间共享变量。只需要你调用getset方法来改变它的值即可。

// Capture a variable to be modified in the Task callbacks.
final Capture<Integer> successfulSaveCount = new Capture<Integer>(0);

saveAsync(obj1).onSuccessTask(new Continuation<ParseObject, Task<ParseObject>>() {
  public Task<ParseObject> then(Task<ParseObject> obj1) throws Exception {
    successfulSaveCount.set(successfulSaveCount.get() + 1);
    return saveAsync(obj2);
  }
}).onSuccessTask(new Continuation<ParseObject, Task<ParseObject>>() {
  public Task<ParseObject> then(Task<ParseObject> obj2) throws Exception {
    successfulSaveCount.set(successfulSaveCount.get() + 1);
    return saveAsync(obj3);
  }
}).onSuccessTask(new Continuation<ParseObject, Task<ParseObject>>() {
  public Task<ParseObject> then(Task<ParseObject> obj3) throws Exception {
    successfulSaveCount.set(successfulSaveCount.get() + 1);
    return saveAsync(obj4);
  }
}).onSuccess(new Continuation<ParseObject, Void>() {
  public Void then(Task<ParseObject> obj4) throws Exception {
    successfulSaveCount.set(successfulSaveCount.get() + 1);
    return null;
  }
}).continueWith(new Continuation<Void, Integer>() {
  public Integer then(Task<Void> ignored) throws Exception {
    // successfulSaveCount now contains the number of saves that succeeded.
    return successfulSaveCount.get();
  }
});

取消Task

如果想要取消Task,需要先创建一个CancellationTokenSource,并把token传给所有你想要取消的创建Task的方法,之后只要调用cancel()就会结束所有与该token关联的Task。

CancellationTokenSource cts = new CancellationTokenSource();

Task<Integer> stringTask = getIntAsync(cts.getToken());

cts.cancel();

取消异步任务需要修改方法接受一个CancellationToken并调用isCancellationRequested()来决定什么时候终止操作。

/**
 Gets an Integer asynchronously.
 */
public Task<Integer> getIntAsync(final CancellationToken ct) {
  // Create a new Task
  final TaskCompletionSource<Integer> tcs = new TaskCompletionSource<>();

  new Thread() {
    @Override
    public void run() {
      // Check if cancelled at start
      if (ct.isCancellationRequested()) {
        tcs.setCancelled();
        return;
      }

      int result = 0;
      while (result < 100) {
        // Poll isCancellationRequested in a loop
        if (ct.isCancellationRequested()) {
          tcs.setCancelled();
          return;
        }
        result++;
      }
      tcs.setResult(result);
    }
  }.start();

  return tcs.getTask();
}
上一篇 下一篇

猜你喜欢

热点阅读