Boltsフレームワークで非同期処理を書く
先日、ParseからBoltsというフレームワークが発表されました。発表された時に機能をさらっと見た感じだとJSのpromiseがJavaで使える!continueで繋げるのね、ふむふむーこれは便利ーって思ったんだけど、実際使おうと思ったらけっこう理解が難しかったのと、Parseのクラスを使った例が多くて汎用的に使う例がおまけっぽくしか書かれてなかったのだ誰でもわかるBoltsの説明をしてみます。
準備
以下からBolts-Android.tar.gzをDLして展開し、bolts.jarを取得したらプロジェクトに追加し、ビルドパスを通しておきます。
https://github.com/BoltsFramework/Bolts-Android/releases
実装
Taskの使い方
タスクというとAsyncTaskのdoInBackground()のように、中にメソッドを持っていそうな気がするのですが、BoltsにおけるTaskは非同期処理の管理をするクラスで、JSのPromisesのJava版という感じです。Promisesって名前にしてくれればよかったのにね!
まずはJavaで普通に非同期処理をするメソッドを作ってみましょう。
public void asyncMethod(final String param) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } // 実際の処理はここで終了 } }).start(); return ; // このメソッド自体はすぐにリターン }
Taskに非同期処理の管理を任せるにはこのメソッド内でTaskのインスタンスを生成してメソッドの戻り値でtaskを返し、処理が終了するところでsetResult()でTaskに通知します。
public Task<String> asyncMethod(final String param) { final Task<String>.TaskCompletionSource task = Task.<String> create(); new Thread(new Runnable() { @Override public void run() { Log.v(TAG, "Running task (" + param + ")"); try { Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } task.setResult(param); // エラーの場合はsetError() //task.setError(new RuntimeException("An error message.")); } }).start(); return task.getTask(); }
呼び出し側は返ってきたTaskのcontinueWith()メソッドで終了後の処理を書きます。
asyncMethod("hoge").continueWith(new Continuation<String, Void>() { @Override public Void then(Task<String> task) throws Exception { // 非同期処理の実行が終了した Log.v(TAG, task.getResult()); return null; } });
continueWith()の引数に渡せるのはContinuationというインターフェースを実装したインスタンスです。
JavaだとGenericsとかでどうしても見づらいところはありますが、asyncMethod()が終了したところで then()が呼ばれます。
メソッドチェーンでつなぐ
この場合非同期処理が単発なので、スレッド内に終了後の処理を書くのとそんなに変わらないのですが、この非同期処理を連続させてみるとどうでしょう?
同じメソッドを、パラメータを変えて連続で読んでみます。
public void runChainingTasks() { asyncMethod("foo") .onSuccessTask(new Continuation<String, Task<String>>() { @Override public Task<String> then(Task<String> task) throws Exception { return asyncMethod(task.getResult() + "bar"); } }).onSuccessTask(new Continuation<String, Task<String>>() { @Override public Task<String> then(Task<String> task) throws Exception { return asyncMethod(task.getResult() + "baz"); } }).continueWith(new Continuation<String, Void>() { @Override public Void then(Task<String> task) throws Exception { Log.v(TAG, "Chain finished : " + task.getResult()); return null; } }); }
このようにネストせずに書けます。ネストではなくメソッドチェーンで書けるというところがポイントです。
これをJavaで普通に書こうとすると、以下のようにどんどんネストしていくことになり、書くのも面倒だし可読性も悪くなっていきます。
public void pyramidAsync() { new Thread(new Runnable() { @Override public void run() { // do something 1 new Thread(new Runnable() { @Override public void run() { //do something 2 new Thread(new Runnable() { @Override public void run() { // do something 3 } }).start(); } }).start(); } }).start(); }
複数タスクを連続実行する
非同期処理の数が増えるとこのTaskの利点が活きてきます。
上記のメソッドチェーンと基本は同じですが、forループ内でtaskに.continueWithTask()をつなげていくことで、ある処理をが終わったら次の処理を、それが終わったらまた次を、というように連続して非同期処理を実行できます。
public Task<String> series(List<String> results) { Task<String> task = Task.forResult(null); for (final String result : results) { task = task.continueWithTask(new Continuation<String, Task<String>>() { @Override public Task<String> then(Task<String> task) throws Exception { return asyncMethod(task.getResult() + ", " + result); } }); } return task; }
複数タスクを同時実行する
複数の処理を同時実行する場合、whenAll()というメソッドを使えば、すべての処理が終了した時の待ち合わせが出来ます。
Taskのリストを作って、whenAll()に渡します。
public Task<Void> parallel(List<String> results) { ArrayList<Task<String>> tasks = new ArrayList<Task<String>>(); for (String result : results) { tasks.add(asyncMethod(result)); } return Task.whenAll(tasks); }