squadron 6.0.2 squadron: ^6.0.2 copied to clipboard
Multithreading and worker thread pool for Dart / Flutter, to offload CPU-bound and heavy I/O tasks to Isolate or Web Worker threads.
Squadron - Multithreading and worker pools in Dart #Offload CPU-bound and long running tasks and give your apps some air! Works everywhere: desktop, server, device, browser. Supports native, JavaScript & Web Assembly platforms. |
View latest documentation on GitHub
Getting Started #
- Update your
pubspec.yaml
file to add dependencies tosquadron
andsquadron_builder
:
dependencies:
squadron: ^6.0.0
# ...
# other dependencies used by your app
# ...
dev_dependencies:
build_runner:
squadron_builder: ^6.0.0
# ...
# other dev dependencies used by your app
# ...
- Have dart download and install the dependencies:
dart pub get
Implementing a Service #
Create a class containing the code you want to run in a dedicated thread and make sure you provide squadron
annotations:
- use
SquadronService
for the class; - use
SquadronMethod
for the methods you want to expose.
Service methods must return a Future<T>
, a FutureOr<T>
or a Stream<T>
.
// file hello_world.dart
import 'dart:async';
import 'package:squadron/squadron.dart';
import 'hello_world.activator.g.dart';
part 'hello_world.worker.g.dart';
@SquadronService(baseUrl: '~/workers', targetPlatform: TargetPlatform.vm | TargetPlatform.web)
base class HelloWorld {
@SquadronMethod()
FutureOr<String> hello([String? name]) {
name = name?.trim() ?? 'World';
return 'Hello, $name!';
}
}
Generate the Worker and WorkerPool code #
Have squadron_builder
generate the code with the following command line:
dart run build_runner build
This command will create the worker and worker pool from your service: HelloWorldWorker
and HelloWorldWorkerPool
.
Run your code #
// file main.dart
import 'package:squadron/squadron.dart';
import 'hello_world.dart';
void main() async {
final worker = HelloWorldWorker();
try {
// Squadron will start the worker for you so you don't have to call worker.start()
final message = await worker.hello();
print(message);
} finally {
// make sure the worker is stopped when the program terminates
worker.stop();
}
}
Building for the Web #
If your app runs in a browser, you must compile your code to JavaScript or Web Assembly. When compiling to only one of Javascript or Web Assembly, you must make sure your service @SquadronService()
annotation only references the corresponding TargetPlatform.js
or TargetPlatform.wasm
.
dart compile js .\hello_world.web.g.dart -o ..\web\workers\hello_world.web.g.dart.js
dart compile wasm .\hello_world.web.g.dart -o ..\web\workers\hello_world.web.g.dart.wasm
You can also compile for both targets: at runtime, Squadron will use the workers matching your app's platform. In that case, make sure your service annotation targets platforms TargetPlatform.js | TargetPlatform.wasm
(Squadron also provides shortcut TargetPlatform.web
; TargetPlatform.all
will generate code for native, JavaScript and Web Assembly targets).
Type System #
There are a few constraints to multithreading in Dart:
- Dart threads do not share memory: values passed from one side to the other will typically be cloned. Depending on the implementation, this can impact performance.
- Service methods arguments and return values need to cross thread-boundaries. On Web platforms, the Dart runtime delegates this to the browser which is not aware of Dart's type system. Extra-work is necessary to regain strongly typed data when receiving data.
Squadron provides Converter
s to "convert" data on the receiving-end to regain strong types and enable statically type-safe code. By default, native platforms use the CastConverter
(typically casting data) while Web platforms rely on NumConverter
(also casting data with special care for int
s)
Native Platforms #
On native platforms, it is generally safe to not bother about custom types and cloning. The Dart VM will take care of copying data when necessary, optimize data-transfer when possible (eg. String
s do not require copying), and the object's type is retained.
There are a few constraints on what type of data can be transferred, please refer to SendPort.send() documentation for more information.
On native platforms, Squadron uses a default CastConverter
that typically casts data on the receiving end.
Web Platforms #
Web platforms have stronger constraints when it comes to transferable objects: for more information, please refer to Transferable objects documentation or the HTML specification for transferable objects. There may also be differences between browser flavors and versions.
On Web plaforms, Squadron uses a default NumConverter
that takes care of handling Dart's int
and double
values. Note that JavaScript only supports doubles really, so integral doubles such as 1.0
are considered integers on JavaScript platforms. Web Assembly platforms support both int
and double
as two different types. As a result, integer values sent to Web Assembly worker are received as double
s and must be cast again to int
on the receiving end.
More importantly, custom-types will require marshaling so they can be transferred across worker boundaries. Squadron is not too opinionated and there are various ways to achieve this: eg. using JSON (together with json_serializer
), by implementing marshal
/unmarshal
methods in your data classes, or by using Squadron marshalers.
For instance, to transfer a Dart BigInt
:
class BigIntMarshaler implements GenericMarshaler<BigInt> {
// "const" so it can be used to annotate parameters and return values
const BigIntMarshaler();
@override
dynamic marshal(BigInt data) => data.toString();
@override
BigInt unmarshal(dynamic data) => BigInt.parse(data);
}
Apply the marshaler by annotating BigInt
parameters and return values:
@SquadronService(baseUrl: '~/workers', targetPlatform: TargetPlatform.web)
base class BigIntService {
@SquadronMethod()
@BigIntMarshaler()
FutureOr<BigInt> add(@BigIntMarshaler() BigInt a, @BigIntMarshaler() BigInt b)
=> a + b;
}
squadron_builder
will implement proper conversion.
Thanks! #
- Saad Ardati for his patience and feedback when implementing Squadron into his Flutter application.
- Martin Fink for the feedback on Squadron's
Stream
implementation -- this has resulted in huge progress and a major improvement! - Klemen Tusar for providing a sample Chopper JSON decoder leveraging Squadron.