[Flutter]Flutterの非同期についてもっとまとめてみた
非同期処理とは
- 非同期処理とは:時間が掛かる処理を行う場合に、次の処理(画面出力等)を行いつつ、その処理を待つ必要がある処理=非同期処理
この画像がわかりやすかった
つまり、
- 「同期処理」はコードの上から処理が終わったら次の処理、それが終わったら次、、、といったふうに処理を終えるまで待つ処理。
- 「非同期処理」は1,2,3をそれぞれ開始→各々終わったら結果を返すという処理。
また、
- 普通の関数やメソッドは「同期」処理である。
- 「非同期」処というのは主にFutureやStreamを関数名の前につけた処理である。
非同期のメリットとデメリットについて
-
メリット:無駄な待ち時間が少ないということ
直列に(一つの受付に一列に並んで処理を待ってる状態で)処理たちが並んでいるのが同期処理なのに対し、同時並行で処理を進められるので、「処理たちの受付が何個もある感じ」で処理を進められる。 -
デメリット:待ち合わせや順序性の確保が面倒ということ
前のメソッドの出力結果に依存するなどの「前処理が終わってから」でないと、次を開始できないような場合は、同期や待ち合わせをする必要がある。
Futureのコード例
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という非同期メソッドを呼び出している。
- 非同期のメソッドを定義する場合はasyncのキーワードをメソッド名の後ろに付与する。(ここではasyncFuncの後ろにくっついてる async てやつ)
- 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への変換の仕方
(各処理たちの受付で「処方箋(戻り値)」を出し、返らせる順番やタイミングなどを具体的に決めたい)
やり方は二種類、thenとawaitがある(非同期処理方法は、「asyncとawait」が推奨されている。ネストが少なく、通常の同期処理と同様に記載可能であり、可読性が高いため)
- then(メソッド)
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)。」
その証拠が以下
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、の順に処理が終わっていることがよくわかる
- await(メソッド)
await = 値が確定するまで待つというやり方。
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,の順・コードの上から)で出てきている。非同期で処理を進めるけど、コード順に処理の順番を揃えること(同期的)ができる
以上です。間違えが見つかり次第、或いは知識がアップデートされ次第改訂していきます。🙏
参考記事
Discussion