LoginSignup
1
2

More than 3 years have passed since last update.

あまり使ったことは無いけど、一応Flutterのisolateの内容を理解しておく

Posted at

:book: Flutterの記事を整理し本にしました :book:

  • 本稿の記事を含む様々な記事を体系的に整理し本にまとめました
  • 今後はこちらを最新化するため、最新情報はこちらをご確認くださ
  • 10万文字を超える超大作になっています(笑)

はじめに

  • 使ったことはないんだけど、一応isolateをきちんと理解しておく
  • 現状は、非同期で間に合っているかな。。。

まとめ

Flutterのスレッド

Flutterはシングルスレッド・イベントループという処理方針を取っています。
これは、1つのスレッドがイベントのキューをつくり、それを順番に処理していくという考え方です。

シングルスレッドですべての処理を行おうとすると、CPUが遊んでいる待ち時間に気を使います。
しかし、(既に過去のチャプターで解説したとおり)Flutterには、async/awaitなどを使った非同期処理を行うことができ、シーケンシャルな処理しかできないわけではありません。

ですが一方で、すごく重たい処理を連続で行う場合には、他の処理の進行に影響を及ぼしてしまいます。

Isolate

上記のような非同期ではなく、多重ループのような重たいCPUを使い続ける処理を行う場合には、Isolateを活用することができます。

Isolateは分離という意味です。

基本となるFlutterのMain Isolateとは別管理のリソースをもつIsolateで処理を行うことができます。
ただし、Isolateは互いに"分離"されているので、呼び出し元から呼び出し先のIsolateにアクセスすることはできません。
互いのIsolateはメッセージを送受信して情報をやり取りします。

いつもどおり、HelloWorldのソースを利用しますが、画面はあまり関係ありません。

main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'dart:isolate';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  Future<void> _incrementCounter() async {
    var recivePort = ReceivePort();
    var sendPort = recivePort.sendPort;
    late Capability capability;

    // 子供からメッセージを受け取る
    recivePort.listen((message) {
      print(message);
      recivePort.close();
    });

    final isolate = await Isolate.spawn(child, sendPort);

    Timer(Duration(seconds: 5), () {
      print("pausing");
      capability = isolate.pause();
    });
    Timer(Duration(seconds: 10), () {
      print("resume");
      isolate.resume(capability);
    });

    Timer(Duration(seconds: 15), () {
      print("kill");
      isolate.kill();
    });

    setState(() {
      _counter++;
    });
  }

  static void child(SendPort sendPort) {
    int i = 0;
    // 親にメッセージを送る
    Timer.periodic(Duration(seconds: 1), (timer) => {sendPort.send(i++)});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

まず、Isolate.spawn(child, sendPort)は、新しいisolateを作っています。
この時、第一引数となるisolateのエントリポイントの関数は、staticな関数である必要があります。

child関数は、毎秒値をインクリメントしながら親にデータを通知します。
呼び元の関数は、5秒後に一時停止、10秒後に再開、15秒後に終了させています。

それぞれのisolaterecivePort/sendPortを使い、データをやり取りします。送る方はsend、受け取る方はlistenを使って非同期に行っています。

なお、一時停止する際に、capabilityを受け取り、再開する際にcapabilityを渡すことで、同じ状況で再開することができます。

result.sh
I/flutter (23253): 0
I/flutter (23253): 1
I/flutter (23253): 2
I/flutter (23253): 3
I/flutter (23253): pausing
I/flutter (23253): resume
I/flutter (23253): 4
I/flutter (23253): 5
I/flutter (23253): 6
I/flutter (23253): 7
I/flutter (23253): 8
I/flutter (23253): 9
I/flutter (23253): kill

一時停止されてから、再開されるまでカウントが止まり、再開時は値が続いていることが確認できます。

compute

FlutterではIsolateを非同期関数のように扱えるようにcompute関数が用意されています。

main.dart
void main() { /* 変更が無いため中略 */ }
class MyApp extends StatelessWidget {/* 変更が無いため中略 */ }
class MyHomePage extends StatefulWidget {/* 変更が無いため中略 */}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  Future<void> _incrementCounter() async {
    compute(child, 1).then((result) {
      print(result);
    });
    print("main end");

    setState(() {
      _counter++;
    });
  }

  static int child(int input) {
    sleep(Duration(seconds: 10));
    return input;
  }

  @override
  Widget build(BuildContext context) {/* 変更が無いため中略 */}
}

result.sh
I/flutter (23253): main end
I/flutter (23253): 1
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2