Dartのasync/awaitを理解したい

Keisuke Kawajiri
9 min readFeb 11, 2020

Dartのasync/awaitは、非同期処理を書く際に利用できるキーワードです。理解が曖昧だったので、下記の公式ドキュメントを読んでまとめてみました。

紹介する内容としては次の通りです。

  • 同期処理、非同期処理
  • async/awaitでFutureを扱う
  • async/await forでStreamを扱う

同期処理、非同期処理

同期処理と非同期処理の定義ですが、

  • 同期処理
    自身の処理が完了するまで、次の処理を実行しない
  • 非同期処理
    自身の処理が完了する前に、他の処理を実行できる

具体的を挙げます。
まずは同期処理です。

// 例1:同期処理
void main(){
process1();
process2();
process3();
}
void process1() => print("process1");
void process2() => print("process2");
void process3() => print("process3");

こちらを実行すると、次のように上の処理から順に実行された結果を得ます。

process1
process2
process3

次に非同期処理です。
process2メソッドをFutureを利用して2秒後に process2 と出力する非同期処理に変更しました。

// 例2:非同期処理
void main() {
process1();
process2();
process3();
}
void process1() => print("process1");
void process2() {
//(1)2秒後に”process2”を返すfutureインスタンスを生成する
Future<String> future =
Future.delayed(Duration(seconds: 2), () => "process2");
// (2)非同期処理(= 2秒後に”process2”を返す処理)が完了した後
// に実行する処理を設定する。
future.then((value) => print(value));
}
void process3() => print("process3");

結果は次の通りです。
main関数に実装した処理順とは異なる順番で表示されます。

process1
process3
process2

Dartには、非同期処理を扱うためFutureとStreamクラスがあります。
以下それぞれのasync/awaitキーワードについて説明します。

async/awaitでFutureを扱う

ここで、process2をasync/awaitを用いて書き換えてみます。

Future<void> process2() async {
Future<String> future =
Future.delayed(Duration(seconds: 2), () => "process2");
String value = await future;
print(value);
}

awaitを使うとFutureの非同期処理が完了するまで待ち、さらに非同期処理の結果を取り出してくれます。
次にasync/awaitの使い方と注意点です。

  • asyncをメソッドのbodyの前(=「{」の前)に付与する
  • awaitは、asyncが定義されているメソッド内でのみ使用ができる
  • asyncを定義したメソッドの戻り値は、Futureでラップした値とする

書き方の具体例は下記になります。
赤波線がコンパイルエラーになっている箇所です。

ここで、一旦例2に戻ります。
私は勘違いしていたのですが、async/awaitの書き方に変えた場合、例1と同様に「process1」→「process2」→「process3」の順で出力されるものと思っていました。実際は、例2の実行結果は変わりません。
これは、あくまでもasync/awaitはコードの可読性を上げるための仕組みであり、処理は変わらないからです。呼出し元から見れば、process2メソッド自体は非同期処理のままです。
例1と同様に「process1」→「process2」→「process3」の順で出力させたい場合は、main関数を次のように修正します。

Future<void> main() async {
process1();
await process2();
process3();
}

また、asyncが付与されたメソッドにおいてもawaitが最初に出てくるまでは、同期処理として処理されていきます。
次に例を挙げます。

Future<void> main() async {
process1();
process2();
process3();
}
void process1() => print("process1");
Future<void> process2() async {
Future<String> future =
Future.delayed(Duration(seconds: 2), () => "process2");
String value = await future;
print("test"); // 例2に対して、追加した処理
print(value);
}
void process3()async => print("process3");

この場合、実行結果は次の通りです。

process1
process3
test
process2

次にprocess2メソッドを次のように、testと表示する処理をawaitの上に変えて実行してみます。

Future<void> process2() async {
Future<String> future =
Future.delayed(Duration(seconds: 2), () => "process2");
print("test"); // awaitの上に変える
String value = await future;
print(value);
}

この場合、process3の上にtestが表示されます。

process1
test
process3
process2

async/await forでStreamを扱う

Streamは非同期で発生するイベント処理を扱う時に利用されます。
Streamの場合は、async/await forを使います。
await forの動作は、次のようになります。

  1. Streamがデータを送出するまで、待機する
  2. Streamからデータが送出されて、変数(下記例のnum)にデータが格納されるとループ内の処理を実行する
  3. 1、2をStreamがcloseするまで繰り返す

await forは、await同様にasyncが定義されているメソッド内でのみ使用ができます。

具体例を挙げます。
下記は、1から10の総和を求めています。
getStreamメソッドは、Generatorsを用いて1から10のデータを1秒毎に順に送出するStreamを生成しています。
そして、sumStreamメソッドのawait forのブロックで、streamから送出されるデータの総和を算出しています。このstreamは1秒毎にデータが送出されるので、1秒毎にデータが加算されていきます。

Future<void> main() async{
print(await sumStream());
}
Future<int> sumStream() async{
Stream<int> stream = getStream();
int sum = 0;
await for(int num in stream){
print(sum);
sum += num;
}
return sum;
}
Stream<int> getStream() async*{
for(int i = 1; i <=10; i++){
await Future.delayed(Duration(seconds: 1));
yield i;
}
}

await forの注意点は、Streamがcloseするまでループを繰り返すところです。
例えば、上記例のgetStreamを下記のようにcloseしないStreamに変えると私の環境では強制終了しました。
このような場合、Streamをcloseすることが基本と思いますが、途中でawait forのループを抜けたい(= Streamの購読を途中で解除したい)場合は、breakやreturnを使います。

Stream<int> getStream() {
StreamController<int> controller = StreamController();
int i = 0;
Timer.periodic(Duration(seconds: 1), (timer){
controller.sink.add(++i);
if(i>=10){
// controller.sink.close();あえてcloseしない
timer.cancel();
}
});
return controller.stream;
}

今回は、dartのasync/awaitに関連することを紹介しました。

--

--