isolate_manager

Creator: coderz1093

Last updated:

Add to Cart

Description:

isolate manager

Isolate Manager #





Features #

Supports to create multiple Isolates for:

A single Function: One IsolateManager instance can be used for only one Function, so that the stream can be used easily when using something like a StreamBuilder (Use the@isolateManagerWorker and @isolateManagerCustomWorker annotations).
Multiple Functions: One IsolateManagerShared instance can be used for multiple Functions, it's good for computing multiple Functions and doesn't care much about the stream (Use the @isolateManagerSharedWorker annotation).


Supports Worker on the Web. If the Worker is unavailable in the working browser or is not configured, the Future (and Stream) will be used.
Supports WASM compilation on the Web.
Supports try-catch blocks.
Multiple compute operations are allowed because the plugin will queue the input data and send it to a free isolate later.

Table Of Contents #

Benchmark
Isolate Manager Shared (For Multiple Functions)
Isolate Manager (For A Single Function)

Basic Usage
Custom Function Usage
Progress Values (Receives multiple values from a single compute)


Try Catch Block
Addtional Information
Contributions

Benchmark #
Execute a recursive Fibonacci function 70 times, computing the sequence for the numbers 30, 33, and 36. The results are in microseconds (On Macbook M1 Pro 14-inch 16Gb RAM).

VM




Fibonacci
Main App
One Isolate
Three Isolates
Isolate.run




30
751,364
771,142
274,854
769,588


33
3,189,873
3,185,798
1,152,083
3,214,685


36
13,510,136
13,540,763
4,873,100
13,766,930




Chrome (With Worker supported)




Fibonacci
Main App
One Worker
Three Workers
Isolate.run (Unsupported)




30
2,125,101
547,800
195,101
0


33
9,083,800
2,286,899
803,599
0


36
38,083,500
9,575,899
3,383,299
0



See here for the test details.
IsolateManagerShared Method #
void main() async {
// Create 3 isolateShared to solve the problems
final isolateShared = IsolateManager.createShared(
concurrent: 3,

// Remove this line (or set it to `false`) if you don't want to use the Worker
useWorker: true,

// Add this mappings so we can ignore the `workerName` parameter
// when using the `compute` method.
workerMappings: {
addFuture : 'addFuture',
add : 'add',
}
);


// Compute the values. The return type and parameter type will respect the type
// of the function.
final added = await isolateShared.compute(
addFuture,
[1.1, 2.2],
// workerFunction: 'addFuture', // Ignored because the `workerMappings` is specified
);
print('addFuture: 1.1 + 2.2 = $added');

// Compute the values. The return type and parameter type will respect the type
// of the function.
final added = await isolateShared.compute(
add,
[1, 2],
// workerFunction: 'add', // Ignored because the `workerMappings` is specified
);
print('add: 1 + 2 = $added');
}

@isolateManagerSharedWorker
Future<double> addFuture(List<double> values) async {
return values[0] + values[1];
}

@isolateManagerSharedWorker
int add(List<int> values) {
return values[0] + values[1];
}
copied to clipboard

Important Note: If you want to build functions into js for the Workers:

These functions MUST NOT depend on any Flutter library like dart:ui, material,... The best way is to move these functions into a separate file so we can control the imports easily.
The input parameters and the return type of these functions should be a JSON (or primitive types) to make the Worker work properly.


Run this command to generate a Javascript Worker (named $shared_worker.js inside the web folder):
dart run isolate_manager:generate
copied to clipboard
Add flag --shared if you want to generate only for the IsolateManagerShared.
IsolateManager Method #
Basic Usage #
There are multiple ways to use this package. The only thing to notice is that the function has to be a static or top-level function.
main() async {
final isolate = IsolateManager.create(
fibonacci,

// And the name of the function if you want to use the Worker.
// Otherwise, you can ignore this parameter.
workerName: 'fibonacci',
concurrent: 2,
);

isolate.stream.listen((value) {
print(value);
});

final fibo = await isolate(20);
}

@isolateManagerWorker // Remove this annotation if you don't want to use the Worker
int fibonacci(int n) {
if (n == 0) return 0;
if (n == 1) return 1;

return fibonacci(n - 1) + fibonacci(n - 2);
}
copied to clipboard

Important Note: If you want to build functions into js for the Workers:

These functions MUST NOT depend on any Flutter library like dart:ui, material,... The best way is to move these functions into a separate file so we can control the imports easily.
The input parameters and the return type of these functions should be a JSON (or primitive types) to make the Worker work properly.


Run this command to generate a Javascript Worker:
dart run isolate_manager:generate
copied to clipboard
Add flag --single if you want to generate only for the IsolateManager.
You can restart or stop the isolate using this method:
await isolateManager.restart();
await isolateManager.stop();
copied to clipboard
Custom Function Usage #
You can control everything with this method when you want to create multiple isolates for a function.
Step 1: Create a function of this form #
Let it automatically handles the result and the Exception:
@isolateManagerCustomWorker // Remove this line if you don't want to use the Worker
void customIsolateFunction(dynamic params) {
IsolateManagerFunction.customFunction<int, int>(
params,
onEvent: (controller, message) {
/* This event will be executed every time the `message` is received from the main isolate */
return fibonacci(message);
},
onInitial: (controller, initialParams) {
/* This event will be executed before all the other events and only one time. */
},
onDispose: (controller) {
/* This event will be executed after all the other events and should NOT be a `Future` event */
},
);
}
copied to clipboard
Handle the result and the Exception by your self:
@isolateManagerCustomWorker // Remove this line if you don't want to use the Worker
void customIsolateFunction(dynamic params) {
IsolateManagerFunction.customFunction<Map<String, dynamic>, String>(
params,
onEvent: (controller, message) {
// This event will be executed every time the `message` is received from the main isolate.
try {
final result = fibonacci(message);
controller.sendResult(result);
} catch (err, stack) {
controller.sendResultError(IsolateException(err, stack));
}

// Just returns something that unused to complete this method.
return 0;
},
onInitial: (controller, initialParams) {
/* This event will be executed before all the other events. */
},
onDispose: (controller) {
/* This event will be executed after all the other events. */
},
autoHandleException: false,
autoHandleResult: false,
);
}
copied to clipboard
Step 2: Create an IsolateManager instance for your own function #
final isolateManager = IsolateManager.createCustom(
customIsolateFunction,
initialParams: 'This is the initialParams',

// And the name of the function if you want to use the Worker.
// Otherwise, you can ignore this parameter.
workerName: 'customIsolateFunction',
debugMode: true,
);
copied to clipboard
Now you can use everything as the Basic Usage.
try-catch Block #
You can use try-catch to catch exceptions:
try {
final result = await isolate(-10);
} on SomeException catch (e1) {
print(e1);
} catch (e2) {
print(e2);
}
copied to clipboard
Progress Values #
You can even manage the final result by using this callback, useful when you create your own function that needs to send the progress value before returning the final result:
main() {
// Create an IsolateManager instance.
final isolateManager = IsolateManager.createCustom(progressFunction);

// Get the result.
final result = await isolateManager.compute(100, callback: (value) {
// Condition to recognize the progress value. Ex:
final data = jsonDecode(value);

if (data.containsKey('progress')) {
print('This is a progress value: ${data['progress']}');

// Return `false` to mark this value is not the final.
return false;
}

print('This is a final value: ${data['result']}');

// Return `true` to mark this value is the final.
return true;
});

print(result); // 100
}

// This is a progress function
@isolateManagerCustomWorker // Add this anotation for a custom function
void progressFunction(dynamic params) {
IsolateManagerFunction.customFunction<String, int>(
params,
onEvent: (controller, message) {
// This value is sent as the progress values.
for (int i = 0; i < message; i++) {
final progress = jsonEncode({'progress' : messsage});
controller.sendResult(progress);
}

// This is a final value.
return jsonEncode({'result' : messsage});
},
);
}
copied to clipboard
Additional Information #


The function has to be a static or top-level function.


If you want to build functions into js for the Workers, these functions MUST NOT depend on any Flutter library like dart:ui, material,... The best way is to move these functions into a separate file so we can control the imports easily. In addition, The input parameters and the return type of these functions should be a JSON (or primitive types) to make the Worker work properly


Use queuesLength to get the current number of queued computation.


Use ensureStarted to able to wait for the start method to finish when you want to call the start method manually without await and wait for it later.


Use isStarted to check if the start method is completed or not.


The result that you get from the isolate (or Worker) is sometimes different from the result that you want to get from the return type in the main app, you can use converter and workerConverter parameters to convert the result received from the Isolate (converter) and Worker (workerConverter). Example:


List<String>
main() async {
final isolate = IsolateManager.create(
aStringList,
workerName: 'aStringList',
// Cast to List<String>
workerConverter: (value) => value.cast<String>() as List<String>,
isDebug: true,
);

final listString = ['a', 'b', 'c'];
final result = await isolate.compute(listString);
expect(result, listString);
}

@isolateManagerWorker
List<String> aStringList(List<String> params) {
return params;
}
copied to clipboard


Map<String, int>: Use json for the complicated cases
main() async {
final isolate = IsolateManager.create(
aStringIntMap,
workerName: 'aStringIntMap',
isDebug: true,
);
await isolate.start();

final map = {'a': 1, 'b': 2, 'c': 3};
final result = await isolate.compute(jsonEncode(map));
expect(jsonDecode(result), map);
}

@isolateManagerWorker
String aStringIntMap(String params) {
return params;
}
copied to clipboard


Data flow: Main -> Isolate or Worker -> Converter -> Result


If you want to use Worker more effectively, convert all parameters and results to JSON (or String) before sending them.


The generator options and flags:

--single: Generates single Functions only.
--shared: Generates shared Functions only.
--in <path> (or -i <path>): Inputted folder.
--out <path> (or -o <path>): Outputted folder.
--obfuscate <level>: The obfuscated level of JS (0 to 4). Default is set to 4.
--debug: Keeps the temp files for debugging.



Contributions #


If you encounter any problems or feel the library is missing a feature, feel free to open an issue. Pull requests are also welcome.


If you like my work or the free stuff on this channel and want to say thanks, or encourage me to do more, you can buy me a coffee. Thank you so much!

License

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Customer Reviews

There are no reviews.