squadron 6.0.3 copy "squadron: ^6.0.3" to clipboard
squadron: ^6.0.3 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 logo

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.

Pub Package Dart Platforms Flutter Platforms

License Null Safety Dart Style Pub Points Likes Popularity

Last Commit Dart Workflow Code Lines Code Coverage

View latest documentation on GitHub

Getting Started #

  1. Update your pubspec.yaml file to add dependencies to squadron and squadron_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
  # ...
  1. 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 Converters to "convert" data on the receiving-end to regain strong types and enable statically type-safe code. By default, native platforms use the DirectCastConverter (casting data) while Web platforms rely on CastConverter (casting data as well as items in List/Set/Map) or NumConverter (adding special handling of int/double values).

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. Strings 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 DirectCastConverter that 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 CastConverter (JavaScript runtime) or NumConverter (Web Assembly runtime). On Web Assembly platforms may receive int values as double values because JavaScript only supports doubles and Dart's int is actually a subtype of double. As a result, integer values sent to Web Assembly worker 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 for instance), by implementing marshal()/unmarshal() or toJson()/fromJson() methods in your data classes, or by using Squadron marshalers.

For instance, to transfer a Dart BigInt instance:

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.
159
likes
0
pub points
89%
popularity

Publisher

unverified uploader

Multithreading and worker thread pool for Dart / Flutter, to offload CPU-bound and heavy I/O tasks to Isolate or Web Worker threads.

Repository (GitHub)
View/report issues

Topics

#concurrency #isolate #multithread #web-worker

Funding

Consider supporting this project:

github.com

License

unknown (license)

Dependencies

cancelation_token, logger, meta, using, web

More

Packages that depend on squadron