本記事は下記リンクのDark公式ページのチュートリアルを学習したメモです。

Asychronous programming

非同期プログラミングの重要性

非同期操作を使うと、別の動作が完了するのを待っている間にプログラムが作業を完了できる。

一般的な非同期操作は次のものがある。

  • ネットワーク経由でデータを取得
  • データベースへの書き込み
  • ファイルからのデータ読み込み

このような非同期動作は通常、結果をFutureとして提供するかStreamとして提供する。

非同期結果を使うにはasyncとawaitキーワードを使う必要がある。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String createOrderMessage() {
  var order = fetchUserOrder();
  return 'Your order is: $order';
}

Future<String> fetchUserOrder() =>
    // Imagine that this function is more complex and slow.
    Future.delayed(
      const Duration(seconds: 2),
      () => 'Large Latte',
    );

void main() {
  print(createOrderMessage());
}
1
Your order is: Instance of '_Future<String>'

fetchUserOrder関数は2秒間待機してから文字列を返すようにしている。

処理を完了するにはcreateOrderMessege関数はfetchUserOrderの処理を待つ必要があるが、待たないので文字列を返してもらえない。

createOrderMessageはfetchUserOrderが実行される間、別の作業ができるが、fetchUserOrderはまだ未完了の状態であるため、’_Future'のタイプ名が返ってくる。

futureとは

頭文字が小文字fのfutureは、Futureのインスタンスだ。futureは非同期操作の結果を表しており、未完了または完了の状態をとることができる。

未完了の状態

未完了の状態の非同期関数を呼び出すと、未完了のfutureが返却される。futureは非同期動作が完了するか、エラーがスローされることを待っている。

完了の状態

非同期動作が成功すると、futureは値で終了する。それ以外はエラーで終了する。

値で終了

futureのタイプはFutureで、Tはタイプだ。指定されたタイプで終了する。Futureの場合は完了したときに文字列を返却し、Futureは完了した時の返却値がない。

1
2
3
4
5
6
7
8
void main() {
  fetchUserOrder();
  print('お客様の注文を待っています。');
}

Future<void> fetchUserOrder() {
  return Future.delayed(const Duration(seconds: 2), () => print('コーヒーラテ大盛り!'));
}

上記の内容の結果を考えてみよう。最初にfetchUserOrderを呼び出しているが、2秒間待機になるため、main関数は待たずに次の文字列出力を実施する。

その後、2秒経ってfetchUserOderで文字列が出力される。

DartPadで実行してみると時間差で出力されるのが見れる。

1
2
お客様の注文を待っています。
コーヒーラテ大盛り!

次は例外が発生するサンプルだ。

1
2
3
4
5
6
7
8
9
void main() {
  fetchUserOrder();
  print('お客様の注文を待っています。');
}

Future<void> fetchUserOrder() {
  return Future.delayed(const Duration(seconds: 2), 
        () => throw Exception('ログインに失敗しました!'));
}
1
2
お客様の注文を待っています。
Uncaught Error: Exception: ログインに失敗しました!

2秒後に例外が出力されることが確認できる。

asyncとawait

asyncとawaitの使い方は

asyncは、関数本体の前に追加する。

1
Future<void> func () async {}

awaitは非同期関数呼び出しの前に使う。

1
var result = await AsyncFunc();

最初のサンプルを少し修正してみた。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
String createOrderMessage() {
  var order = fetchUserOrder();
  return 'ご注文内容は$orderですね';
}

Future<String> fetchUserOrder() =>
    // Imagine that this function is more complex and slow.
    Future.delayed(
      const Duration(seconds: 2),
      () {
        print('カフェラテ、グランデお願いします。');
        return 'カフェラテ、グランデ';
      }
    );

void main() {
  print('何を注文しますか?');
  print(createOrderMessage());
  print('かしこまりました!');
}
1
2
3
4
何を注文しますか?
ご注文内容はInstance of '_Future<String>'ですね
かしこまりました!
カフェラテ、グランデお願いします。

注文が済んでもないのに店員さんが勝手に進んだ形だ。お客さんの注文は2秒後に出力される。

上のコードをasyncとawaitを使ってきちんとお客さんの注文を待つようにしてみよう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Future<String> createOrderMessage() async {
  var order = await fetchUserOrder();
  return 'ご注文内容は$orderですね';
}

Future<String> fetchUserOrder() =>
    // Imagine that this function is more complex and slow.
    Future.delayed(
      const Duration(seconds: 2),
      () {
        print('カフェラテ、グランデお願いします。');
        return 'カフェラテ、グランデ';
      }
    );

Future<void> main() async {
  print('何を注文しますか?');
  print(await createOrderMessage());
  print('かしこまりました!');
}
1
2
3
4
何を注文しますか?
カフェラテ、グランデお願いします。
ご注文内容はカフェラテ、グランデですね
かしこまりました!

最初何を注文するか聞いてからawaitを使ってcreateOrderMessageを呼び出しているので処理が終わるまで待つようになる。

createOrderMessageも同様、awaitを使ってfetchUserOrderを呼び出しているので処理が終わるまで待つようになる。

fetchUserOrderは2秒間待機後、注文を言い渡して商品とサイズを返却する。

createOrderMessageは返却された文字列を元に注文内容を確認するメッセージをmain関数に返却する。

mainはもらったメッセージを出力し、最後の文字列を出力する。

次の例を見てみよう。awaitが出てくるまでの処理は同期的に処理される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void countSeconds(int s) {
  for (var i = 1; i <= s; i++) {
    Future.delayed(Duration(seconds: i), () => print(i));
  }
}

Future<void> printOrderMessage() async {
  print('注文しますか?');
  var order = await fetchUserOrder();
  print('注文内容は$orderですね。');
}

Future<String> fetchUserOrder() {
  return Future.delayed(
    const Duration(seconds: 4),
    () => 'カフェラテ、グランデ',
  );
}

void main() async {
  countSeconds(4);
  await printOrderMessage();
}
1
2
3
4
5
6
注文しますか?
1
2
3
4
注文内容はカフェラテ、グランデですね。

この結果が理解できるか?

最初にcountSecondsが呼ばれ、forが実行された。

ループの中でiに該当する秒を待って出力するようにして終了している。

同期で動いているので待機を待たずにcountSecondsは終わる。

その後、awaitでprintOrderMessageが呼ばれるので終わるまで待つことになる。

printOrderMessageの中で最初のprintは同期なのですぐ実行されて結果的に一番最初に出力される。

その後、fetchUserOrderが4秒間待機になり、その間にcountSecondsで実行されたprintが順次に出力され、4秒後、注文内容が返却されてから全てが終わることになる。

最後に

まだ該当公式チュートリアルの内容は終わってないが、今日は遅くなったのでここまでする。

次回は残りのエラーハンドリングとチュートリアルにはないがStreamについても調べておきたい。

タグ:

カテゴリー:

更新日時:

コメントする