Bolts框架中Task的用法
项目地址:Bolts-Android
Task是为了更好的书写复杂异步操作而设计的,运用了Javascript的Promise思想。
如果想要构建一个响应迅速的Android应用,那么你就不能在UI线程中运行任何耗时操作,避免阻塞UI线程,这也就意味着你需要在后台中执行大量的操作。为了让这一过程变得更简单,我们增加了这个叫做Task
的类。一个Task
代表一个异步操作。通常情况下,我们会写一个方法返回一个Task
,这个Task
具有继续操作任务结果的能力。当这个Task
被方法返回时,它已经开始执行它的任务了。Task
不与特定的线程模型进行绑定:它代表要被完成的操作,而不是执行操作的地点。Task
与其他异步方法(Callbacks
、AsyncTask
)相比有许多优势。
-
Task
占用更少的系统资源,因为Task
在等待其他Tasks的时候不占用线程。 - 执行一系列
Task
的时候不需要像你使用CallBack
时一样写出金字塔式的嵌套代码。 -
Task
是可以组合的,允许你执行分支、并行和复合型的错误处理,不需要用到嵌套的代码和各种复杂命名的CallBack
。 - 你可以有序的整理基于任务的代码并执行它们,而不是将你的逻辑分散在凌乱的回调函数中。
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;
}
});
异常处理
在书写你的应用时,谨慎的选择调用continueWith
或onSuccess
可以帮助你控制异常的传递方式。使用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
一开始,你可能只是使用类似findAsync
或saveAsync
这种方法返回的简单的Task
。但是,对于更高级的方案,你可能想创建自定义的Task
。为了实现这个需求,你创建了一个TaskCompletionSource
。这个对象允许你创建新的Task
并且控制它的执行结果是已完成或取消。在你创建了一个Task
之后,你需要调用setResult
,seterror
,setCancelled
来触发它之后的操作。
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
方法来同步执行多个Task
。Task.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
所有continueWith
和onSuccess
方法都可将java.util.concurrent.Executor
的实例作为参数传入。这让你可以控制后续任务在哪里执行。默认状态下,Task.call()
会在当前线程执行Callable
,Task.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_EXECUTOR
,Task.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
这个类的原因,它允许你在各个回调之间共享变量。只需要你调用get
,set
方法来改变它的值即可。
// 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();
}