Bolts框架中Task的用法

项目地址:Bolts-Android

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

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

  • 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;
  }
});

异常处理

在书写你的应用时,谨慎的选择调用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();
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,043评论 25 707
  • 译序 本指南根据 Jakob Jenkov 最新博客翻译,请随时关注博客更新:http://tutorials.j...
    高广超阅读 5,052评论 1 68
  • /** * 截取中文字符串 * @param string $string 中文字符串 * @param int ...
    上善若水_900e阅读 155评论 0 0
  • 今天上午第一节晨读课一下课,我们就匆匆下楼,到一楼实验室听王心理老师讲书法课,王老师是我们学校的退休美术老...
    自由自在绿叶阅读 833评论 0 0
  • 『论病气』这个问题困扰了我很久也思考了很久,至今也找不到解决的办法。 病气是什么?简单讲就是已经进入人体形成疾病的...
    Zoe妈inUK阅读 2,176评论 1 0