2022年9月2日金曜日

【Dart】非同期処理、async/await、compute

 https://cbtdev.net/dart-compute/


Flutter
 

Flutter の非同期処理では async/await を使いますが、UI が固まるような CPU 負荷が高い処理をする場合は compute が便利なので、これらの使用方法について解説します。

async/await

サーバーの応答を待つだけといった、CPU 負荷は低いけど時間がかかる可能性がある処理で使われます。(メインスレッドで実行されるので CPU 負荷が高い場合は固まります。)

Future<Hoge> myFunc() async {
  // await で何かの処理を待つ
  var hoge = await hogehoge();
  return hoge;  
}

Flutter ではお馴染みだと思うので詳細は省きます。

compute

計算コストが高く、UI が固まるような CPU 負荷が高い処理では compute を利用すると便利です。(この処理は別スレッドで実行されます)

インポートするライブラリ

compute 関数を利用する場合は以下のライブラリをインポートします。

import 'package:flutter/foundation.dart';

使い方

compute で呼び出すグローバル関数、又は static な関数を定義します。

以下は String を入力として、Stringを返すグローバル関数の例です。

// compute で処理する関数
String calc(String data) {
  // ここで何か重い処理
  return data;
}

ウィジェット内のコードで以下のように呼び出せば、別スレッドで実行されます。第一引数が関数名で、第二引数が関数に渡すデータです。

data = await compute(calc, data);

compute の戻り値は Future なので、以下のように書くこともできます。

compute(calc, data)
  .then((result) {
    // 結果が返ってきた時に呼ばれる
    data = result;
  });
// この下は非同期で直ぐ呼ばれる

compute は Dart のスレッド処理である Isolate を扱いやすいようにラップしたものなので、単独のクローバル関数又は Static な関数であること、関数内で更にスレッドを駆使するような複雑な処理はできない、などいくつか制限がありますが、直列的な単純処理ならこれで十分かと思います。

Future.wait

複数の非同期処理をまとめて完了待機したい場合は、Future.wait を使います。

// compute で書いてますが async/await でも同じです
var future1 = compute(calc1, _data1);
var future2 = compute(calc2, _data2);
var future3 = compute(calc3, _data3);

// まとめてリストにする
var futureList = Future.wait([future1, future2, future3]);
// 全ての処理を待機
await futureList.then((results) => _data = results[0]);
// 処理後の更新など
setState(() {});

results もリストで返ってくるので、適宜処理します。

サンプルコード

async/await と compute の動作を比較できるサンプルコードです。

同じグローバル関数を「async/await」と「compute」でそれぞれ実行します。

前者は UI のインジケーターアニメーションがガッツリ固まりますが、後者では UI が固まらずに処理できているのが確認できると思います。

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart'; // compute
import 'dart:io'; // sleep

// async/await と compute 共通で呼び出すグローバル関数
Future<String> calc(String data) async {
  // ここで重い処理(今回は3秒sleepで代用)
  sleep(Duration(seconds: 3));
  data = '処理完了';
  return data;
}

void main() => runApp(MyApp());

// MyApp ウィジェットクラス
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.light(),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

// MyHomePage ウィジェットクラス
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

// MyHomePage ステートクラス
class _MyHomePageState extends State<MyHomePage> {
  // ダミーデータ
  String _dummyData = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            CircularProgressIndicator(),

            // ----- async/await の場合 -----
            RaisedButton(
              child: Text('async/await'),
              onPressed: () async {
                _dummyData = 'スタート';
                setState(() {});

                _dummyData = await calc(_dummyData);
                setState(() {});
              },
            ),

            // ----- compute の場合 -----
            RaisedButton(
              child: Text('compute'),
              onPressed: () async {
                _dummyData = 'スタート';
                setState(() {});

                _dummyData = await compute(calc, _dummyData);
                setState(() {});
              },
            ),

            // ----- 結果表示 -----
            Text('$_dummyData'),
          ],
        ),
      ),
    );
  }
}

実行画面:

※ 比較のためグローバル関数を Future/async で書いていますが、compute 前提なら不要です。

0 コメント:

コメントを投稿