LoginSignup
5
3

More than 5 years have passed since last update.

Dart async-awaitのサンプル

Last updated at Posted at 2014-12-20

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/

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

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程度のオーバーヘッドと考えてよさそうです。
※他にもオーバーヘッド等あるのかもしれませんが、現状の感覚として。。

5
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3