🔖

[Flutter]Flutterの非同期についてもっとまとめてみた

2022/07/01に公開

非同期処理とは

  • 非同期処理とは:時間が掛かる処理を行う場合に、次の処理(画面出力等)を行いつつ、その処理を待つ必要がある処理=非同期処理

この画像がわかりやすかった
サンマの塩焼き
つまり、

  • 「同期処理」はコードの上から処理が終わったら次の処理、それが終わったら次、、、といったふうに処理を終えるまで待つ処理。
  • 「非同期処理」は1,2,3をそれぞれ開始→各々終わったら結果を返すという処理。

また、

  • 普通の関数やメソッドは「同期」処理である。
  • 「非同期」処というのは主にFutureStreamを関数名の前につけた処理である。

非同期のメリットとデメリットについて

  • メリット:無駄な待ち時間が少ないということ
    直列に(一つの受付に一列に並んで処理を待ってる状態で)処理たちが並んでいるのが同期処理なのに対し、同時並行で処理を進められるので、「処理たちの受付が何個もある感じ」で処理を進められる。

  • デメリット:待ち合わせや順序性の確保が面倒ということ
    前のメソッドの出力結果に依存するなどの「前処理が終わってから」でないと、次を開始できないような場合は、同期や待ち合わせをする必要がある。

Futureのコード例

main.dart
import 'dart:async';
import 'dart:io';
class Async {
  void asynctest2() {
    print("method begin");
    print(DateTime.now().toString());
    print("data1 start");
    print(asyncFunc("data1", 3));
    print("data2 start");
    print(asyncFunc("data2", 2));
    print("data3 start");
    print(asyncFunc("data3", 1));
  }
  // timeの時間分スリープし、その後現在時間を返す関数
  Future<String> asyncFunc(String name, int time) async {
    return Future.delayed(Duration(seconds: time), () {
      return name + ":" + DateTime.now().toString();
    });
  }

asynctest2の中で、asyncFuncという非同期メソッドを呼び出している。

  1. 非同期のメソッドを定義する場合はasyncのキーワードをメソッド名の後ろに付与する。(ここではasyncFuncの後ろにくっついてる async てやつ)
  2. Future<String> というのは関数の型。「Futureクラス型」ということ。そして関数、メソッドの戻り値を<>ここに入れている。(ここではStirng型)
    将来的にはString型の戻り値ですよ〜ってこと。
I/flutter (15069): method begin
I/flutter (15069): 2021-04-10 14:51:43.855018
I/flutter (15069): data1 start
I/flutter (15069): Instance of 'Future<String>'
I/flutter (15069): data2 start
I/flutter (15069): Instance of 'Future<String>'
I/flutter (15069): data3 start
I/flutter (15069): Instance of 'Future<String>'

asynctest2は、それぞれのメソッドを呼び出しますが、Future<String>というものを仮で受け取ったあとに、すぐにその次の処理に移っています。
時間の表示がないので分かりづらいですが、各メソッドからFuture<String>が返ってくるのはsleepする時間を待たずに(処理を完了させずに)返ってきています
つまり、呼び出し側はそれぞれのメソッドを呼び出した後に、次の処理に移れるため非同期の処理を実現することができます。

とのこと。

処理の戻り値が来る、具体的なタイミングやStringへの変換の仕方

(各処理たちの受付で「処方箋(戻り値)」を出し、返らせる順番やタイミングなどを具体的に決めたい)
やり方は二種類、thenawaitがある(非同期処理方法は、「asyncとawait」が推奨されている。ネストが少なく、通常の同期処理と同様に記載可能であり、可読性が高いため)

  1. then(メソッド)
main.dart
import 'dart:async';
import 'dart:io';
class Async {
  void asynctest3() async {
    print("method begin");
    print(DateTime.now().toString());
    print("data1 start");
    //Future型のresult1の(result1.)then()メソッドを呼び出す
    Future<String> result1 = asyncFunc("data1", 3);
    result1.then((result) {
      print(result);
    });
    print("data2 start");
    Future<String> result2 = asyncFunc("data2", 2);
    result2.then((result) {
      print(result);
    });
    print("data3 start");
    Future<String> result3 = asyncFunc("data3", 1);
    result3.then((result) {
      print(result);
    });
  }
  // timeの時間分スリープし、その後現在時間を返す関数
  Future<String> asyncFunc(String name, int time) async {
    return Future.delayed(Duration(seconds: time), () {
      return name + ":" + DateTime.now().toString();
    });
  }

Futureにはthenというメソッドが準備されています。
このメソッドは、処理が終わった時に呼び出されます。
今回はシンプルに、値が確定したらprintしています

要するに、宣言した順関係なく、
thenメソッド=「処理が終わったら表示する(受付終わったらByeBye)。」
その証拠が以下

thenthenI/flutter (15069)
I/flutter (15069): 2021-04-10 14:52:49.054188
I/flutter (15069): data1 start
I/flutter (15069): data2 start
I/flutter (15069): data3 start
I/flutter (15069): data3:2021-04-10 14:52:50.070597
I/flutter (15069): data2:2021-04-10 14:52:51.069553
I/flutter (15069): data1:2021-04-10 14:52:52.067980

結果を見てみると、まずdata1,2,3がstartし、その後終わった順(呼び出した順ではなく)にdata3,2,1と文字列を表示しています。
1,2,3はそれぞれ3秒,2秒,1秒スリープするようになっていますので、逆順で出力されています。
asynctest3と3つのasyncFuncはそれぞれ独立して非同期になっていることがわかります。

とのこと。data1、2、3、の順にスリープ時間を3、2、1で設定したので、
(asyncFunc(String name, int time)の部分で第二引数に int timeとして設定した)

I/flutter (15069): data3:2021-04-10 14:52:50.070597
I/flutter (15069): data2:2021-04-10 14:52:51.069553
I/flutter (15069): data1:2021-04-10 14:52:52.067980
(さっきの下三行)

こうやってData3、2、1、の順に処理が終わっていることがよくわかる

  1. await(メソッド)

await = 値が確定するまで待つというやり方。

main.dart
import 'dart:async';
import 'dart:io';
class Async {
  void asynctest4() async {
    print("method begin");
    print(DateTime.now().toString());
    print("data1 start");
    print(await asyncFunc("data1", 3));
    print("data2 start");
    print(await asyncFunc("data2", 2));
    print("data3 start");
    print(await asyncFunc("data3", 1));
  }
  // timeの時間分スリープし、その後現在時間を返す関数
  Future<String> asyncFunc(String name, int time) async {
    return Future.delayed(Duration(seconds: time), () {
      return name + ":" + DateTime.now().toString();
    });
  }

awaitというキーワードを使うと、その場で非同期関数の終了を待ちます。

I/flutter (15069): method begin
I/flutter (15069): 2021-04-10 14:54:09.874908
I/flutter (15069): data1 start
I/flutter (15069): data1:2021-04-10 14:54:12.887107
I/flutter (15069): data2 start
I/flutter (15069): data2:2021-04-10 14:54:14.895110
I/flutter (15069): data3 start
I/flutter (15069): data3:2021-04-10 14:54:15.910756

awaitの場合は、data1をはじめた後に、data2の開始を行うのではなく、data1のasyncFuncが終わるのを待つ
つまり、待ち合わせを行い同期的に動いてる。
(同じ処理の中で一緒にスタートした処理さん達を待って、みんなで一緒に受付終了し、みんなで一緒に処方箋もらってる感じ!)

I/flutter (15069): data1 start
I/flutter (15069): data1:2021-04-10 14:54:12.887107
I/flutter (15069): data2 start
I/flutter (15069): data2:2021-04-10 14:54:14.895110
I/flutter (15069): data3 start
I/flutter (15069): data3:2021-04-10 14:54:15.910756

この部分にもあらわれているように、最後に処理さん達が出ていく順番は宣言された順(Data1,2,3,の順・コードの上から)で出てきている。非同期で処理を進めるけど、コード順に処理の順番を揃えること(同期的)ができる

以上です。間違えが見つかり次第、或いは知識がアップデートされ次第改訂していきます。🙏
参考記事

GitHubで編集を提案

Discussion