Dart/Flutterのisolateを理解
isolate = dartのマルチスレッド的なやつ。
- main()はmain isolate
- 子のisolateを生成した親のisolateは子のisolateの終了を待つことができる
- 親子isolate間でping pong的な相互通信もできる
worker isolateで1秒かかる処理を待つ
import 'dart:isolate'; void main() async { debugPrint("${DateTime.now()} start"); final result = await _runInBackground(); debugPrint("${DateTime.now()} $result"); debugPrint("${DateTime.now()} end"); } Future<String> _runInBackground() async { // 以下の3行はおまじないと思うことにする // 大事なのは_runInIsolateで実際のバックグラウンド処理をするということ final p = ReceivePort(); await Isolate.spawn(_runInIsolate, p.sendPort); // worker isolateの生成 return await p.first as String; } Future<void> _runInIsolate(SendPort p) async { // 1秒待つ。実際の実装ではここで時間のかかる処理をする。 await Future.delayed(const Duration(seconds: 1)); const result = "worker isolate終了"; Isolate.exit(p, result); }
これくらいならisolateを使うまでもないはず。次のような相互通信などで必要になる。
isolateで相互通信する
やること
- mainのバックグラウンド処理からメッセージを投げると遅延してworker isolateから応答が来る
- mainのバックグラウンド処理がもういいよとworker isolateに伝えるまで、そのやりとりが続く
- mainは1回ごとのworker isolateの応答を受け取りprintする
import 'dart:isolate'; import 'package:async/async.dart'; void main() async { debugPrint("${DateTime.now()} start ping pong"); // await forで通信が終わるまでループする await for (final message in _pingPongInBackground()) { debugPrint("${DateTime.now()} $message"); } debugPrint("${DateTime.now()} end ping pong"); } // 戻り値は、Stream<1回の通信で返す値の型>、お尻はasyncではなくasync*と書く Stream<String> _pingPongInBackground() async* { final p = ReceivePort(); await Isolate.spawn(_runInWorkerIsolate, p.sendPort); // 呼び出し元のawait forに順次値を返すStreamを生成 final stream = StreamQueue<dynamic>(p); // 最初にworker isolateから通信するためのPortを受け取る(Aと対応) SendPort sendPort = await stream.next; // worker isolateとの相互通信を10回繰り返す for (var i = 0; i < 10; i++) { // pingをworker isolateに投げる sendPort.send("ping $i"); // worker isolateの応答を待つ String pong = await stream.next; // Streamへの返却値はyieldで返す // main()のawait forで順次受け取る yield pong; } // worker isolateにもうpingは送らないから終了していいよと伝える sendPort.send(null); // Stream終了。main()のawait forループを終了させる await stream.cancel(); } // worker isolateの処理 Future<void> _runInWorkerIsolate(SendPort p) async { final commandPort = ReceivePort(); // 最初に自分と通信するためのPortを渡す(A) p.send(commandPort.sendPort); // ping待機ループ await for (final ping in commandPort) { if (ping is String) { // ping受信を検知 // 1秒遅延。実際の実装ではここで通信処理など時間のかかる処理をする await Future.delayed(const Duration(seconds: 1)); // 応答を返す p.send("pong ${DateTime.now()} for $ping"); } else if (ping == null) { // 通信終了していいよと言われたらループを抜ける break; } } Isolate.exit(); }