Orientação gerada a partir de informações públicas. Apenas para referência.
O pacote isolate_manager permite a criação de isolados de longa duração em aplicativos Flutter, suportando funções únicas e múltiplas. Ele oferece integração eficiente com Web Workers na web por meio de um gerador e facilita a compilação para WASM para computação de alto desempenho. Projetado para extensão e aprimoramento do Dart, simplifica a gestão de isolados em plataformas como Android, iOS, Web, macOS, Windows e Linux. Ideal para deslocar tarefas intensivas e manter a responsividade da interface.
Casos de uso
Executando cálculos em segundo plano
Processamento assíncrono de grandes conjuntos de dados
Implementando Web Workers na web
Executando módulos WASM de forma eficiente
Gerenciando trabalhadores de isolamento persistentes
Melhorando o desempenho do aplicativo com processamento paralelo
Principais recursos
Suporta isolados de longa duração
Compatibilidade com Web Worker via gerador
Suporte à compilação WASM
Consistência entre plataformas
Isolamento eficiente de função
Gerenciamento automático de recursos
Indicado para
Aplicativos críticos de desempenho
Operações intensivas em dados
tarefas intensivas em computação baseadas na web
processos em segundo plano de longa duração
Desenvolvedores direcionados para múltiplas plataformas
Aplicativos que exigem execução isolada
Considerações
Requer compreensão de isolados e passagem de mensagens
Pode aumentar o uso de memória com muitos isolados
A implantação na web exige uma configuração de build adequada
Não adequado para tarefas simples de uma vez
A configuração do WASM exige ferramentas adicionais
O comportamento específico da plataforma pode exigir testes
README original
Snapshot do README original do repositório-fonte vinculado. Consulte o repositório para o conteúdo mais recente.
Isolate Manager
Isolate Manager is a powerful Flutter/Dart package designed to simplify concurrent programming using isolates. It offers robust cross-platform support, including native, web (via JavaScript Workers), and WebAssembly (WASM).
Why Isolate Manager?
Effortless Concurrency: Takes the complexity out of Dart's isolates for smooth background processing.
Truly Cross-Platform: Write your concurrent code once and run it seamlessly on Dart VM, Web (auto-compiles to JS Workers), and WASM.
Robust & Safe: Provides built-in type and exception safety mechanisms, especially crucial for web worker communication.
Optimized Performance: Features smart task queuing, priority handling, and customizable strategies to fine-tune execution.
Flexible Isolate Types: Choose from one-off, multi-function, or single-function isolates to best suit your task's lifecycle.
Features
Versatile Isolate Management:
One-off Isolates: Perfect for single, intensive computations. Supports web workers.
Multi-Function Isolates: Reuse a single isolate for various functions, minimizing overhead.
Single-Function Isolates: Dedicate an isolate to a specific, continuous task or data stream.
Cross-Platform by Design:
Web & WASM Ready: Automatically compiles isolate functions into JavaScript Workers for web deployment.
Graceful Fallback: Defaults to Future/Stream if Web Workers are unavailable.
Intelligent Queue System:
Automatic task queuing.
Support for priority tasks.
Customizable queue overflow strategies.
Type & Exception Safety:
Utilize specialized ImType wrappers (ImNum, ImString, etc.) for reliable data transfer with web workers.
Propagate custom exceptions across isolate boundaries, even on the web.
Developer-Friendly Extras:
Custom Isolate Functions: Gain full control over the isolate's lifecycle and communication.
Progress Reporting: Send intermediate updates from long-running tasks.
Codeless Web Workers: Generate necessary JavaScript worker files without build_runner.
Built-in Benchmark: Compare performance of different concurrency models.
Getting Started
1. Add Dependencies
Add the following to your pubspec.yaml:
dependencies:isolate_manager:^latest_version# Use the latest versiondev_dependencies:isolate_manager_generator:^latest_version# Required for web worker generation
Then, run dart pub get or flutter pub get.
2. Important Prerequisite for Isolate Functions
Functions intended to run in an isolate must be:
A static method within a class, OR
A top-level function (defined outside any class).
Additionally, annotate these functions with @pragma('vm:entry-point') to prevent them from being removed by tree-shaking during compilation.
Note: Worker annotations like @isolateManagerWorker, @isolateManagerSharedWorker, and @isolateManagerCustomWorker are provided by the isolate_manager package.
Make sure to import them if you use them in your code.
import'package:isolate_manager/isolate_manager.dart';
@pragma('vm:entry-point')
intsum(List<int> numbers) {
return numbers.reduce((a, b) => a + b);
}
3. Platform-Specific Setup
Mobile & Desktop (VM)
No additional setup is required!
Web (JavaScript Workers)
To use isolates on the web, your Dart functions need to be compiled into JavaScript Workers.
Annotate Your Functions:
Use specific annotations on the functions you want to be available as web workers. Run the generator after adding or modifying these.
@isolateManagerWorker: For one-off or single-function isolates.
@isolateManagerSharedWorker: For shared, multi-function isolates.
@isolateManagerCustomWorker: For custom isolate functions where you manage communication manually.
Data Transfer Limitations:
Functions for web workers must not depend on Flutter-specific libraries (e.g., dart:ui).
Only Dart primitives (num, String, bool, null), and Map or List collections containing these primitives, are directly transferable.
For other data types, use the provided ImType wrappers or serialize/deserialize your data manually.
Generate JS Workers:
Run the following command in your terminal:
Type Handling: When using WASM, all int types (including those in collections) are treated as double. Isolate Manager provides a built-in converter to handle this automatically; you can disable it by setting enableWasmConverter: false if needed.
Transferables: On WASM, transferables (zero-copy data transfer) add unnecessary overhead and provide no performance benefit. By default, transferables are omitted when targeting WASM. You can re-enable them by setting enableWasmTransferables: true if needed.
Development Server Headers: If your app hangs when running with flutter run -d chrome --wasm, you might need to set specific headers. Try:
flutter run -d chrome --wasm --web-header=Cross-Origin-Opener-Policy=same-origin --web-header=Cross-Origin-Embedder-Policy=require-corp
Usage Examples
The optional debugName parameter defaults to normal for create/run, custom for createCustom/runCustomFunction, and shared for createShared.
One-off Isolate (Simple Task)
Ideal for fire-and-forget computations.
import'package:isolate_manager/isolate_manager.dart';
@pragma('vm:entry-point')
@isolateManagerWorker// For web worker generationintfibonacciRecursive(int n) {
if (n < 2) return n;
returnfibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}
voidmain() async {
// Option 1: Explicit worker parameters (useful if function name differs or for clarity)final result1 = awaitIsolateManager.run(
() => fibonacciRecursive(40), // The actual function to execute
workerName: 'fibonacciRecursive', // Name used for JS worker mapping
workerParameter: 40, // Parameter for the JS worker
);
print('Fibonacci(40) - Option 1: $result1');
// Option 2: Automatic worker mapping (concise)final result2 = awaitIsolateManager.runFunction(fibonacciRecursive, 40);
print('Fibonacci(40) - Option 2: $result2');
}
Long-Lived Multi-Function Isolate
Reuse a single isolate for multiple different computations.
import'package:isolate_manager/isolate_manager.dart';
@pragma('vm:entry-point')
@isolateManagerSharedWorkerFuture<double> addNumbersFuture(List<double> values) async {
return values[0] + values[1];
}
@pragma('vm:entry-point')
@isolateManagerSharedWorkerintmultiplyNumbers(List<int> values) {
return values[0] * values[1];
}
voidmain() async {
final sharedIsolate = IsolateManager.createShared(
concurrent: 2, // Number of tasks this isolate can handle concurrently
useWorker: true, // Enable web worker usage if on web
workerMappings: {
// Map Dart function references to their JS worker names
addNumbersFuture: 'addNumbersFuture',
multiplyNumbers: 'multiplyNumbers',
},
);
// Optional: Listen for intermediate values (if any function sends them)
sharedIsolate.stream.listen((value) {
print('Intermediate value from shared isolate: $value');
});
final sumResult = await sharedIsolate.compute(addNumbersFuture, [10.5, 20.3]);
print('Sum (shared): 10.5 + 20.3 = $sumResult');
final productResult = await sharedIsolate.compute(multiplyNumbers, [7, 6]);
print('Product (shared): 7 * 6 = $productResult');
await sharedIsolate.stop(); // Important to release resources// Or use `sharedIsolate.restart()` to restart the isolate
}
Long-Lived Single-Function Isolate
Dedicate an isolate to a single, potentially continuous, function.
import'package:isolate_manager/isolate_manager.dart';
@pragma('vm:entry-point')
@isolateManagerWorkerintcomplexCalculation(int initialValue) {
// Simulate a task that might send progress updates or run for a whileint result = initialValue;
for (int i = 0; i < 5; i++) {
result += i * 2;
// If this were a custom function, you could send progress here
}
return result;
}
voidmain() async {
final singleFuncIsolate = IsolateManager.create(
complexCalculation, // The function this isolate is dedicated to
workerName: 'complexCalculation', // For JS worker
concurrent: 1, // Typically 1 for a single dedicated function
);
// Optional: Listen for intermediate values
singleFuncIsolate.stream.listen((value) {
print('Intermediate value from single-function isolate: $value');
});
final calculationResult = await singleFuncIsolate.compute(10); // `compute` is callableprint('Complex Calculation Result: $calculationResult');
await singleFuncIsolate.stop(); // Release resources// Or use `singleFuncIsolate.restart()` to restart the isolate
}
Custom Function & Error Handling
For fine-grained control over the isolate's behavior, including sending multiple results or handling initialization/disposal.
import'dart:convert';
import'package:isolate_manager/isolate_manager.dart';
@pragma('vm:entry-point')
@isolateManagerWorker// For webintfibonacci(int n) {
if (n < 0) throwArgumentError('Input cannot be negative.');
if (n < 2) return n;
returnfibonacci(n - 1) + fibonacci(n - 2);
}
@pragma('vm:entry-point')
@isolateManagerCustomWorkervoidcustomFibonacciWorker(dynamic params) {
IsolateManagerFunction.customFunction<int, int>(
params,
onEvent: (controller, message) {
try {
final result = fibonacci(message);
controller.sendResult(result); // Send the final result
} catch (err, stack) {
// Send an IsolateException for structured error handling
controller.sendResultError(IsolateException(err.toString(), stack.toString()));
}
return0; // Indicates completion for this event
},
onInit: (controller) {
print('Custom Fibonacci Worker: Initialized');
// Perform any setup logic here
},
onDispose: (controller) {
print('Custom Fibonacci Worker: Disposed');
// Perform any cleanup logic here
},
autoHandleException: false, // Set to true to let IsolateManager handle basic errors
autoHandleResult: false, // Set to true to let IsolateManager handle basic result sending
);
}
voidmain() async {
final customIsolate = IsolateManager.createCustom(
customFibonacciWorker,
workerName: 'customFibonacciWorker', // For JS Worker
debugMode: true, // Enable more logging
);
try {
final result = await customIsolate.compute(10);
print('Custom Fibonacci(10): $result');
final resultNegative = await customIsolate.compute(-5); // This will throwprint('Custom Fibonacci(-5): $resultNegative');
} onIsolateExceptioncatch (e) {
print('Caught IsolateException: ${e.error}');
// print('Stack trace: ${e.stacktrace}');
} catch (e) {
print('Caught other error: $e');
} finally {
await customIsolate.stop();
}
}
Advanced Capabilities
Smart Queue Management
Control how tasks are queued and processed when multiple requests are made to an isolate.
Priority Tasks: Add priority: true when calling compute or run to move a task to the front of its queue.
Queue Size Limit: Set maxCount in IsolateManager.create, createShared, or createCustom constructors to limit the number of pending tasks.
Queue Strategies: Define behavior when maxCount is reached:
UnlimitedStrategy(): (Default) No limit on queued tasks.
DropNewestStrategy(): Removes the most recently added task.
DropOldestStrategy(): Removes the oldest task in the queue.
RejectIncomingStrategy(): Rejects any new tasks if the queue is full.
Custom Queue Strategy: For custom logic, extend QueueStrategy<R, P>:
classMyCustomStrategy<R, P> extendsQueueStrategy<R, P> {
@overrideboolcontinueIfMaxCountExceeded() {
// Access `queues`, `queuesCount`, `maxCount`if (queuesCount >= maxCount && queues.isNotEmpty) {
// Example: Allow if the oldest task is low priority (pseudo-code)// if (queues.first.priority == false) {// print('Custom strategy: Dropping oldest to make space.');// queues.removeFirst(); // You'd need to manage this carefully// return true; // Allow the new task// }returnfalse; // Reject by default if full
}
returntrue; // Allow if not full
}
}
Note: Modifying queues directly in continueIfMaxCountExceeded requires careful implementation to ensure consistency.
Progress Updates
Receive intermediate results from a task before it completes. This is typically used with IsolateManager.createCustom.
In this example, progress updates are sent as JSON strings for demonstration purposes.
// In your main Dart code:voidmain() async {
final isolate = IsolateManager.createCustom(
longRunningTaskWithProgress,
workerName: 'longRunningTaskWithProgress', // For JS Worker
);
print('Starting task with progress updates...');
final result = await isolate.compute(
100, // Example parameter for the task
callback: (dynamic value) {
// `value` is what `controller.sendResult()` sends from the isolatetry {
final data = jsonDecode(value asString); // Assuming JSON string for progressif (data.containsKey('progress')) {
print('Progress: ${data['progress']}%');
returnfalse; // Indicates this is a progress update, not the final result
} elseif (data.containsKey('finalResult')) {
print('Final result package received: ${data['finalResult']}');
returntrue; // Indicates this is the final result
}
} catch (e) {
print('Error decoding progress/result: $e');
returntrue; // Treat as error, stop listening
}
returntrue; // Default to stop if format is unexpected
},
);
print('Task completed with final processed value: $result');
await isolate.stop();
}
// In your isolate function (e.g., custom worker):@pragma('vm:entry-point')
@isolateManagerCustomWorkervoidlongRunningTaskWithProgress(dynamic params) {
IsolateManagerFunction.customFunction<String, int>(
params,
onEvent: (controller, totalSteps) {
for (int i = 0; i <= totalSteps; i += 10) {
final progressReport = jsonEncode({'progress': i});
controller.sendResult(progressReport); // Send progress update
}
// Send the final resultreturnjsonEncode({'finalResult': totalSteps});
},
);
}
Ensuring Type Safety (Web)
When working with Web Workers, data transfer is restricted. IsolateManager provides ImType wrappers for common types to ensure they are correctly (de)serialized.
import'package:isolate_manager/isolate_manager.dart';
@pragma('vm:entry-point')
@isolateManagerWorkerImMapprocessDataWeb(ImList numbers) {
// 1. Unwrap to get standard Dart types (List<dynamic> in this case)final nativeList = numbers.unwrap asList<dynamic>;
// 2. Process the datafinal processedMap = <ImType, ImType>{};
for (var i = 0; i < nativeList.length; i++) {
final numVal = nativeList[i] asnum; // Ensure type
processedMap[ImString('item_$i')] = ImNum(numVal * 2);
}
// 3. Wrap the result in an ImTypereturnImMap(processedMap);
}
voidmain() async {
// On web, this will use the JS worker if generated.final isolate = IsolateManager.create(
processDataWeb,
workerName: 'processDataWeb',
);
try {
// 1. Wrap your input datafinal inputData = ImList.wrap([1, 2.5, 3]);
// 2. ComputefinalImMap result = await isolate.compute(inputData) asImMap;
// 3. Unwrap the resultfinal nativeResult = result.unwrap asMap<dynamic, dynamic>;
nativeResult.forEach((key, value) {
print('Web Processed: ${key} -> ${value}');
});
} onUnsupportedImTypeExceptioncatch (e) {
print('Error: Unsupported type used with ImType. ${e.message}');
} catch (e) {
print('An error occurred: $e');
} finally {
await isolate.stop();
}
}
Available ImType wrappers (for non-nullable types only):
ImNum(double value) / ImNum(int value)
ImString(String value)
ImBool(bool value)
ImList(List<ImType> value) or ImList.wrap(List<dynamic> nativeList)
ImMap(Map<ImType, ImType> value) or ImMap.wrap(Map<dynamic, dynamic> nativeMap)
An UnsupportedImTypeException is thrown if ImList.wrap or ImMap.wrap encounters a type that cannot be converted.
Zero-Copy Data Transfers (Transferables)
For large Uint8List or ByteBuffer payloads, pass a transferables list to compute() to enable zero-copy transport instead of copying bytes across isolate boundaries.
On native (VM) the buffer is wrapped in a TransferableTypedData and sent O(1) — no byte copying. On web (dart2js) the ArrayBuffer is transferred via the postMessage transfer list, also O(1), and the source buffer is detached (its lengthInBytes becomes 0) after the call returns.
Auto-extraction with sendResultWithAutoTransfer
Inside a custom isolate, the AutoTransferExtension recursively finds every Uint8List / ByteBuffer in the result and transfers them automatically — no manual bookkeeping needed:
import'package:isolate_manager/isolate_manager.dart';
@pragma('vm:entry-point')
voidprocessingWorker(dynamic params) {
final controller =
IsolateManagerController<Map<String, Object?>, Uint8List>(params);
controller.onIsolateMessage.listen((input) {
final output = Uint8List(input.length);
for (var i = 0; i < output.length; i++) {
output[i] = (input[i] + 1) % 256;
}
// Finds all Uint8List/ByteBuffer in the map and transfers them zero-copy.
controller.sendResultWithAutoTransfer({'result': output, 'size': output.length});
});
controller.initialized();
}
Pros, cons, and platform behaviour
Native (VM)
Web — dart2js
Web — dart2wasm
No transferables
Bytes deep-copied O(n)
Bytes serialised & copied O(n)
Bytes copied O(n)
transferables: [data.buffer]
Zero-copy via TransferableTypedData (O(1) transport; small codec overhead)
Zero-copy via ArrayBuffer transfer (O(1)); source buffer detached
⚠ No benefit — WASM linear memory must be copied to the JS heap regardless
Pre-built TransferableTypedData
Fastest — skips codec overhead entirely
N/A
N/A
Native (VM):
Eliminates the O(n) copy for large buffers; measurable improvement at ~1 MB+.
Pre-building TransferableTypedData before calling compute() removes the codec overhead and is the fastest option.
⚠️ Small buffers (< ~100 KB) may see no net gain or a slight regression due to codec overhead.
⚠️ The source buffer is consumed by TransferableTypedData; do not reuse the original Uint8List after calling compute() with it as a transferable.
Web — dart2js:
Source ArrayBuffer is transferred in O(1); the worker receives the original memory.
Large speedups (2–10×) for MB-range payloads compared to copy-based transfer.
⚠️ Source buffer is neutered after compute() returns — data.buffer.lengthInBytes becomes 0. Keep a reference to the result instead.
Web — dart2wasm:
⚠️ WASM uses linear memory that is opaque to the JS engine. Every transfer still requires a copy from the WASM heap to a JS ArrayBuffer, so using transferables adds codec overhead with no speed benefit.
Prefer omitting transferables when targeting WASM.
⚠️ Note: Transferables are omitted by default when targeting WASM to avoid codec overhead. To enable transferables on WASM, set enableWasmTransferables: true in the IsolateManager constructor.
Handling Exceptions (Web)
To ensure custom exceptions are correctly propagated from Web Workers:
Define a Custom IsolateException: Extend IsolateException.
Register the Exception: Use IsolateManager.registerException() in your main application.
Throw the Custom Exception: In your isolate function.
import'package:isolate_manager/isolate_manager.dart';
// 1. Define your custom exceptionclassMyCustomWebExceptionextendsIsolateException{
constMyCustomWebException(super.error, [super.stacktrace]);
@overrideStringget name => 'MyCustomWebException'; // Crucial for deserialization
}
@pragma('vm:entry-point')
@isolateManagerWorkerImNumtaskThatThrowsCustom(ImNum input) {
if (input.unwrap == 0) {
throwconstMyCustomWebException('Input cannot be zero!');
}
returnImNum(100 / (input.unwrap asnum));
}
voidmain() async {
// 2. Register the exception type (typically in your app's initialization)IsolateManager.registerException(
(message, stackTrace) => MyCustomWebException(message, stackTrace),
);
final isolate = IsolateManager.create(
taskThatThrowsCustom,
workerName: 'taskThatThrowsCustom', // For JS Worker
);
try {
print('Trying with valid input...');
final result = await isolate.compute(ImNum(10));
print('Result: ${(result as ImNum).unwrap}'); // Should be 10print('\nTrying with input that causes custom exception...');
await isolate.compute(ImNum(0)); // This will throw
} onMyCustomWebExceptioncatch (e, s) {
print('Caught MyCustomWebException!');
print('Error: ${e.error}');
// print('Stack: $s'); // s is the stacktrace from the main isolate// print('Original Isolate Stack: ${e.stacktrace}'); // stacktrace from the worker
} catch (e) {
print('Caught an unexpected error: $e');
} finally {
await isolate.stop();
}
}
// No boilerplate — the manager already knows which worker to spin up.awaitIsolateManager.runFunction(fibonacciRecursive, 40);
await sharedIsolate.compute(addNumbersFuture, [10.5, 20.3]);
await singleFuncIsolate.compute(10);
If you’re wondering what “magic” was removed:
// Long form (now unnecessary):// final result = await IsolateManager.run(// () => fibonacciRecursive(40),// workerName: 'fibonacciRecursive',// workerParameter: 40,// );
Web Worker Generator
Use the isolate_manager:generate command to compile annotated Dart functions into JavaScript workers for web deployment.
See Web (JavaScript Workers) for web platform setup.
Command:
dart run isolate_manager:generate
Flags & Options:
--single: Generate only for functions annotated with @isolateManagerWorker.
--shared: Generate only for functions annotated with @isolateManagerSharedWorker.
--in <path> (or -i <path>): Specify the input directory to scan for annotated functions (default: lib).
--out <path> (or -o <path>): Specify the output directory for generated JS files (default: web).
--obfuscate <level>: Set JavaScript obfuscation level (0-4, default is 4 for smallest size). 0 means no obfuscation.
--debug: Retain temporary files created during generation for debugging purposes.
--worker-mappings-experiment=lib/main.dart (Experimental): Attempt to auto-generate workerMappings for IsolateManager.createShared by scanning the specified Dart file.
You can also pass additional arguments to the underlying Dart compiler by adding -- after the generator command. For example:
dart run isolate_manager:generate -- --omit-implicit-checks --no-source-maps
These arguments will be forwarded to the Dart process (useful for dart2js / dart compile js or other Dart compiler options).
Additional Tips
Queue Length: Check isolateManagerInstance.queuesLength to get the current number of tasks in the queue.
Manual Start Control: Use isolateManagerInstance.ensureStarted() to await explicit initialization if needed. Check isolateManagerInstance.isStarted to see if the isolate is ready.
Data Flow with Converters (WASM): When WASM type converters are active, the data flow is: Main Isolate → Worker → Main Isolate → Converter → Final Result.
Performance Benchmark
The following benchmarks demonstrate the performance of recursive Fibonacci calculations across different concurrency approaches and environments. Measurements are in microseconds (µs) and represent the median of 30 samples to ensure stability. Benchmarked on a MacBook M1 Pro 14" with 16GB RAM.