Flutterの非同期処理の実装について解説!

ブログ

FlutterアプリケーションはDart言語を使用しますが、実はこのDart言語はシングルスレッドのプログラミング言語です。

これが何を意味するか?

アプリケーションコードが全て同じスレッド上で動作するということは、IO処理・HTTPリクエスト(レスポンス)など、時間のかかる処理でスレッドが中断してしまうという事です。

これを防ぐためにDartは非同期処理を行うための様々な手段を提供しているわけです。今回はその代表的な手段であるFuture(async・await)をご紹介してみます。

非同期処理なしのサンプルコード

例として、まずは時間のかかる処理を非同期処理を行わずに実装してみましょう。

import 'dart:io';

void longRunningOperation() {
  for (int i = 0; i < 5; i++) {
    sleep(Duration(seconds: 1));
    print("index: $i");
  }
}

main() {
  print("start of long running operation");
  longRunningOperation();
  print("continuing main body");
  for (int i = 10; i < 15; i++) {
    sleep(Duration(seconds: 1));
    print("index from main: $i");
  }
  print("end of main");
}

内容は簡単ですね。インデックスを1秒おきにログ出力しているだけの処理です。こちらの出力結果が以下です。

I/flutter (20590): start of long running operation
I/flutter (20590): index: 0
I/flutter (20590): index: 1
I/flutter (20590): index: 2
I/flutter (20590): index: 3
I/flutter (20590): index: 4
I/flutter (20590): continuing main body
I/flutter (20590): index from main: 10
I/flutter (20590): index from main: 11
I/flutter (20590): index from main: 12
I/flutter (20590): index from main: 13
I/flutter (20590): index from main: 14
I/flutter (20590): end of main

これもまあそうだよね、となると思います。

ただ、ここで注目したいのが以下の部分で、

I/flutter (20590): start of long running operation
I/flutter (20590): index: 0
I/flutter (20590): index: 1
I/flutter (20590): index: 2
I/flutter (20590): index: 3
I/flutter (20590): index: 4
I/flutter (20590): continuing main body

ここで、main関数の処理を中断してlongRunningOperation()内の処理を行っていることがわかると思います。

最初に説明したように、Dartはシングルスレッドなので非同期処理を挟まないとこのように時間のかかる処理を挟むと本来やりたい処理(今回のケースでいうとmain()の処理)が中断してしまうということがわかると思います。

これはかなり不便で、例えばmain関数内で画面の表示を行う場合であれば、この時間のかかる処理を待ってからでないと画面の表示が行われないということになってしまいます。

というわけで、これを非同期処理にしてみましょう。

非同期処理を入れたサンプルコード(1)

import 'dart:io';

Future<void> longRunningOperation() async {  <-変更
  for (int i = 0; i < 5; i++) {
    Future.delayed(Duration(seconds: 1)); <-変更
    print("index: $i");
  }
}

main() {
  print("start of long running operation");
  longRunningOperation();
  print("continuing main body");
  for (int i = 10; i < 15; i++) {
    sleep(Duration(seconds: 1));
    print("index from main: $i");
  }
  print("end of main");
}

longRunningOperationの戻りの型をFuture<void>として、またログに出力するsleepをFuture.delayedに変更しています。こちらに関しては後述。

出力結果は以下になります。

I/flutter (22736): start of long running operation
I/flutter (22736): continuing main body
I/flutter (22736): index from main: 10
I/flutter (22736): index from main: 11
I/flutter (22736): index from main: 12
I/flutter (22736): index from main: 13
I/flutter (22736): index from main: 14
I/flutter (22736): end of main
I/flutter (22736): index: 0
I/flutter (22736): index: 1
I/flutter (22736): index: 2
I/flutter (22736): index: 3
I/flutter (22736): index: 4

出力結果が変わりましたね。

main()関数の出力が先に来ており、後でlongRunningOperation()の出力が行われています。

これは何が起きているか?という話なのですが、これはsleep(Duration)の仕様が関係しています。

実はsleep(Duration)は非同期処理に対応しておらず、スレッドを占有してしまう仕様になっています。非同期処理を行う際はFuture.delayed(Duration)を使用する必要があります。

つまり、先ほどの変更で非同期処理としてlongRunningOperation()が一旦awaitしたところまではいいのですが、main関数側のsleep()がスレッドを占有してしまった関係でlongRunningOperation()が最後に呼ばれる形式になってしまったというわけですね。

非同期処理を入れたサンプルコード(2)

というわけで、main()関数側も変更してみようと思います。

import 'dart:io';

Future<void> longRunningOperation() async {
  for (int i = 0; i < 5; i++) {
    await Future.delayed(Duration(seconds: 1));
    print("index: $i");
  }
}

main() async { <-変更
  print("start of long running operation");
  longRunningOperation();
  print("continuing main body");
  for (int i = 10; i < 15; i++) {
    await Future.delayed(Duration(seconds: 1)); <-変更
    print("index from main: $i");
  }
  print("end of main");
}

main()関数側のsleep(Duration)をFuture.delayed(Duration)に変更することで、スレッドを占有させることなく非同期処理を行うようにしてみました。

こちらの出力結果が結果が以下です。

I/flutter (22943): start of long running operation
I/flutter (22943): continuing main body
I/flutter (22943): index: 0
I/flutter (22943): index from main: 10
I/flutter (22943): index: 1
I/flutter (22943): index from main: 11
I/flutter (22943): index: 2
I/flutter (22943): index from main: 12
I/flutter (22943): index: 3
I/flutter (22943): index from main: 13
I/flutter (22943): index: 4
I/flutter (22943): index from main: 14
I/flutter (22943): end of main
Application finished.

かなりマルチスレッドっぽい出力結果になりましたね。

longRunningOperation()とmain()で処理が終了(=1秒経った)ものから、相手側の処理を待たずに出力が行われていることがわかります。

まとめ

このように、Futureを戻り値としてメソッドを記述することで非同期処理を簡単に実現することができます。

非同期処理はかなり使う処理になるので、使いこなせるようにしていきたいものです。

コメント