dart-concurrency-isolates について
Dart 開発者向けに、Isolate を使った並行処理の実装方法を案内するスキルです。一度限りのタスクと長時間稼働するワーカーの選択、メッセージ通信、終了処理を扱います。
主な機能
- 01Isolate 戦略の選択
- 02`Isolate.run()` による単発タスク
- 03`Isolate.spawn()` による常駐ワーカー
- 04ポートを使った双方向通信
- 05リクエスト追跡とエラー処理
- 06ワーカーとポートの終了処理
活用シーン
- 01大きな JSON の解析をメイン Isolate から分離
- 02継続的なデータ処理の実行
- 03データベース接続の維持
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ dart-lang/ skills --skill dart-concurrency-isolatesインストール後、Claude Code で/dart-concurrency-isolatesを入力して読み込まれたか確認します。
詳細
Dart の並行処理と Isolate の管理
目次
基本ガイドライン
- Isolate のメモリ: Isolate 間で共有メモリは一切ないものとして扱います。Isolate 間の通信はメッセージパッシングのみで行います。
- データ転送: Isolate 間で大きな可変オブジェクトを渡すことは避けます。シリアライズのオーバーヘッドを抑えるため、シンプルなデータ型または不変レコードを優先します。
- リソース管理: メモリリークを防ぐため、不要になった Isolate とポートは必ず終了させます。
- プラットフォームの制約: Dart Web プラットフォームでは Isolate を使用しないでください。Web は JavaScript にコンパイルされ、JavaScript では代わりに Web Workers が使用されます。
- 関連スキル: Main Isolate で実行する標準的な非同期処理(
Future、Stream、async/await)については、dart-async-programmingを参照してください。
適切な Isolate 戦略の選択
条件分岐を適用して、適切な Isolate の実装方法を判断します。
- 単純な単発のバックグラウンドタスクを実行する場合(例:大きな JSON ペイロードを 1 つ解析する、ファイルを圧縮する):
Isolate.run()を使用します。 - 複雑で長時間実行するバックグラウンドワーカーを実行する場合(例:継続的なデータ処理、データベース接続の維持):
Isolate.spawn()を使用し、SendPortとReceivePortを手動で管理します。
単発タスクの実装
Isolate.run() を使用すると、Isolate を起動して関数を実行し、結果を取得した後、自動的に Isolate を終了できます。
- トップレベル関数、静的メソッド、またはクロージャを
Isolate.run()に渡します。 - Main Isolate で結果を
awaitします。
Future<Map<String, dynamic>> parseLargeJson(String jsonString) async {
// Spawns isolate, runs decode, returns result, and terminates automatically.
return await Isolate.run(() => jsonDecode(jsonString) as Map<String, dynamic>);
}
長時間実行ワーカーの実装
長時間実行するワーカー Isolate との双方向通信を確立するには、SendPort と ReceivePort を手動で管理します。
RawReceivePortで初期化する: Main Isolate でRawReceivePortを使用し、起動時のロジックと継続的なメッセージ処理を分離します。- 双方向通信を確立する:
Isolate.spawn()を介して、Main Isolate のSendPortを Worker Isolate に渡します。Worker Isolate は独自のReceivePortを作成し、そのSendPortを Main Isolate に送り返す必要があります。 - リクエストとレスポンスを対応付ける: 一意の ID を使用する
Completerマップで、Worker Isolate に送信した非同期リクエストを追跡し、レスポンスの受信時に完了させます。 - エラーを処理する: Worker Isolate で例外をキャッチし、
RemoteErrorオブジェクトとして送り返します。 - 終了処理: Worker Isolate に専用のシャットダウンメッセージを送信して
ReceivePortを閉じ、すべてのリクエストが完了したら Main Isolate のReceivePortを閉じます。
ワークフロー
タスクの進捗:長時間実行ワーカーのセットアップ
長時間実行ワーカー Isolate を実装する際の進捗を追跡するには、このチェックリストをコピーしてください。
- Isolate の管理をカプセル化する
Workerクラスを作成します。 -
RawReceivePortを使用してワーカーから最初のSendPortを取得する静的なspawn()メソッドを実装します。 -
RawReceivePort.sendPortとワーカーのエントリーポイントメソッドを渡してIsolate.spawn()を呼び出します。 - ワーカーのエントリーポイントメソッド(
_startRemoteIsolate)を実装します。 - ワーカー内に
ReceivePortを作成し、そのSendPortを Main Isolate に送り返します。 - ワーカーの
ReceivePortにリスナーを設定し、受信コマンドを処理します。 - Main Isolate の
ReceivePortにリスナーを設定し、レスポンスを処理してCompleterインスタンスを完了させます。 - シャットダウンコマンドを送信してすべてのポートを閉じる
close()メソッドを実装します。 - バリデーターを実行 → エラーを確認 → 修正します(メモリリークがなく、すべてのポートが適切に閉じることを確認します)。
例
堅牢な長時間実行ワーカーの実装
長時間実行するワーカー Isolate と堅牢な双方向通信を行うには、このパターンを使用します。
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
class JsonWorker {
final SendPort _commands;
final ReceivePort _responses;
final Map<int, Completer<Object?>> _activeRequests = {};
int _idCounter = 0;
bool _closed = false;
JsonWorker._(this._responses, this._commands) {
_responses.listen(_handleResponsesFromIsolate);
}
static Future<JsonWorker> spawn() async {
final initPort = RawReceivePort();
final connection = Completer<(ReceivePort, SendPort)>.sync();
initPort.handler = (initialMessage) {
final commandPort = initialMessage as SendPort;
connection.complete((
ReceivePort.fromRawReceivePort(initPort),
commandPort,
));
};
try {
await Isolate.spawn(_startRemoteIsolate, initPort.sendPort);
} catch (e) {
initPort.close();
rethrow;
}
final (ReceivePort receivePort, SendPort sendPort) = await connection.future;
return JsonWorker._(receivePort, sendPort);
}
Future<Object?> parseJson(String message) async {
if (_closed) throw StateError('Worker is closed');
final completer = Completer<Object?>.sync();
final id = _idCounter++;
_activeRequests[id] = completer;
_commands.send((id, message));
return await completer.future;
}
void _handleResponsesFromIsolate(dynamic message) {
final (int id, Object? response) = message as (int, Object?);
final completer = _activeRequests.remove(id)!;
if (response is RemoteError) {
completer.completeError(response);
} else {
completer.complete(response);
}
if (_closed && _activeRequests.isEmpty) _responses.close();
}
static void _startRemoteIsolate(SendPort sendPort) {
final receivePort = ReceivePort();
sendPort.send(receivePort.sendPort);
receivePort.listen((message) {
if (message == 'shutdown') {
receivePort.close();
return;
}
final (int id, String jsonText) = message as (int, String);
try {
final jsonData = jsonDecode(jsonText);
sendPort.send((id, jsonData));
} catch (e) {
sendPort.send((id, RemoteError(e.toString(), '')));
}
});
}
void close() {
if (!_closed) {
_closed = true;
_commands.send('shutdown');
if (_activeRequests.isEmpty) _responses.close();
}
}
}
コメント
Markdown 対応