Dart async-awaitのサンプル

# Ecma-408 2nd revision

DartはEcma-408という名前で仕様が公開されていますが、
最近、Ecma-408の2nd revisionができたようです。
公式ブログでも宣伝されてます。
http://news.dartlang.org/2014/12/enums-and-async-primitives-in-dart.html

2nd revisionで追加されたのは、以下の3機能になります。

1. Enumerations (enum) 
2. Asynchrony (async, await, and more)
3. Deferred loading (import ... deferred as)

enumはDart 1.9で実装済み
async-awaitは、1.8でphase 1まで実装済み
deferred loadingは、1.6で実装済み

dartのasync-awaitの実装状況とか、私が過去にまとめた資料は以下です。
http://nothingcosmos.blog52.fc2.com/blog-entry-180.html
https://dl.dropboxusercontent.com/u/58430726/DartVM/src/2014dart.html

# async-awaitのサンプル

async-awaitは、公式ページで使い方が説明されてました。
https://www.dartlang.org/articles/await-async/

それとは別に、挙動を確認しつつ、
自分なりに多少実用的かな？というサンプルを作ってみました。こちらです。。

```dart:fibo_async.dart
import 'dart:async';

int fibo_async(int n) async {
  if (n < 2) {
    return n;
  } else {
    return await fibo_async(n-1) + await fibo_async(n-2);
  }
}

int count = 0;
int fibo(int n) {
  count++;
  if (n<2) {
    return n;
  } else {
    return fibo(n-1) + fibo(n-2);
  }
}

loop() async {
  for(;;) {
    await new Future.delayed(const Duration(seconds: 1), () => print("loop"));
  }
}

calcSync(int n) async {
    count = 0;
    var watch = new Stopwatch()..start();
    var ret = fibo(n);
    print("elapsed_ms=${watch.elapsedMilliseconds}, fibo($n)=${ret}, count=$count");
}

calc(int n) async {
    var watch = new Stopwatch()..start();
    fibo_async(n).then((p) => print("elapsed_ms=${watch.elapsedMilliseconds}, fibo($n)=$p"));
}


main() {
  loop();
//  calcSync(42);
//  calcSync(28);
//  calcSync(29);
//  calcSync(30);
//  calcSync(31);
//  calc(28);
//  calc(29);
  calc(30);
//  calc(31);
 }
```

何をしているかというと、毎度お馴染みのfibo()関数をasync-awaitで修飾してみたのが、fibo_async()関数です。
fibo_async()が実行されるのと並行して、loop()で1秒ごとに割り込んでいます。
loop()で1秒ごとに割り込むのと並行して、fibo_async()で少しずつ計算をすすめ、約10秒後に計算結果を返しています。
実行結果は私の環境ではこんな感じです。

//calcSync()のコメントを外せば、最初にfibo()を計算するため、loop()の割り込みを数秒間ブロックすることになります。

```:
$ dart --enable-async async_fibo.dart
loop
loop
loop
loop
loop
loop
loop
loop
loop
elapsed_ms=9347, fibo(30)=832040
loop
loop
loop
```

# メリット

async-awaitを使用すれば、fibo()のようにCPUを食ってビジーになりやすい処理を適度に分割することができます。
結果として、ビジーのまま応答を返さない挙動を改善することができます。

なんでこんな面倒な方法になるのかというと、DartはThreadをサポートしておらず、
ただ1つのmain threadが、EventLoopしながら処理しています。

Dartのシングルスレッド実行に関しては、公式サイトを翻訳された下記の記事が大変わかりやすいです。
Dart Advent Calendar 2014 の 14日目の記事です。
http://qiita.com/takyam/items/6ad155678c95bba4047f

上記から引用しますが、

>
タスクをスケジュールする場合は、いくつかのルールに従ってください：
...
アプリが応答可能な状態を保つために、イベントループ上での数値計算タスクを避けてください
数値計算タスクを行う場合、isolateかworkerを追加で作成してください。

上記の数値計算タスクが、fibo()に該当しますね。
本来であれば、こういった数値計算処理専用のIsolateを別途立てて、
そこにタスクを投げ結果をfutureで待つのが一般的だと思います。

Isolateを新規作成すると、1つスレッドを作成し、その上で稼働するVMもどき
(Heap,GC,JITコンパイラ,Coreやクラスの読み込み結果などが、Isolateごとに新規作成)
を作成します。これはDart起動時にIsolateを1つ作成するのとまったく同じです。
そのため、Multithreadingのような挙動を期待する場合、Isolateを複数作成し、メッセージでやりとりする必要があります。


# デメリット
デメリットとしては、シングルスレッドのEventLoopを回りながら、
awaitのcontext間でcontext switchのようなことを行うため、オーバーヘッドが発生します。

上記のサンプルでは、fibo(30)を実行していますが、通常では10ms程度の処理が、
async-awaitで修飾すると10,000 ms程度かかります。

fibo(30)では、2,692,537回ほど再帰呼び出しされるようですが、
その中でawaitは2個挿入しています。

await 1個につき、context switchに2microsecond程度のオーバーヘッドと考えてよさそうです。
※他にもオーバーヘッド等あるのかもしれませんが、現状の感覚として。。