{"id":13548763,"url":"https://github.com/d-markey/squadron","last_synced_at":"2025-03-29T19:02:18.907Z","repository":{"id":41904375,"uuid":"410297787","full_name":"d-markey/squadron","owner":"d-markey","description":"Multithreading and worker thread pool for Dart / Flutter, to offload CPU-bound and heavy I/O tasks to Isolate or Web Worker threads (JS+WASM).","archived":false,"fork":false,"pushed_at":"2025-03-16T23:47:47.000Z","size":104400,"stargazers_count":105,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-22T18:01:38.622Z","etag":null,"topics":["dart","isolate","parallelism-library","thread","webworker"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/squadron","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/d-markey.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"d-markey"}},"created_at":"2021-09-25T14:45:06.000Z","updated_at":"2025-03-18T05:40:56.000Z","dependencies_parsed_at":"2024-02-11T01:24:06.700Z","dependency_job_id":"53b7b755-2fe3-472f-a2a1-1b662c83b3b6","html_url":"https://github.com/d-markey/squadron","commit_stats":{"total_commits":329,"total_committers":4,"mean_commits":82.25,"dds":0.0942249240121581,"last_synced_commit":"05052a0e95c7fabb73251fbdb2959e321e79fae6"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-markey%2Fsquadron","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-markey%2Fsquadron/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-markey%2Fsquadron/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-markey%2Fsquadron/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/d-markey","download_url":"https://codeload.github.com/d-markey/squadron/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246230523,"owners_count":20744347,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dart","isolate","parallelism-library","thread","webworker"],"created_at":"2024-08-01T12:01:14.202Z","updated_at":"2025-03-29T19:02:18.891Z","avatar_url":"https://github.com/d-markey.png","language":"Dart","readme":"\u003ctable\u003e\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/d-markey/squadron/main/squadron_logo.svg\" width=\"120\" alt=\"Squadron logo\" /\u003e\n\u003c/td\u003e\n\u003ctd\u003e\n\n## **Squadron - Multithreading and worker pools in Dart**\n\nOffload CPU-bound and long running tasks and give your apps some air!\n\nWorks everywhere: desktop, server, device, browser.\n\nSupports native, JavaScript \u0026 Web Assembly platforms.\n\n[![Pub Package](https://img.shields.io/pub/v/squadron)](https://pub.dev/packages/squadron)\n[![Dart Platforms](https://badgen.net/pub/dart-platform/squadron)](https://pub.dev/packages/squadron)\n[![Flutter Platforms](https://badgen.net/pub/flutter-platform/squadron)](https://pub.dev/packages/squadron)\n\n[![License](https://img.shields.io/github/license/d-markey/squadron)](https://github.com/d-markey/squadron/blob/master/LICENSE)\n[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)\n[![Dart Style](https://img.shields.io/badge/style-lints-40c4ff)](https://pub.dev/packages/lints)\n[![Pub Points](https://img.shields.io/pub/points/squadron)](https://pub.dev/packages/squadron/score)\n[![Likes](https://img.shields.io/pub/likes/squadron)](https://pub.dev/packages/squadron/score)\n[![Downloads](https://img.shields.io/pub/dm/squadron)](https://pub.dev/packages/squadron/score)\n\n[![Last Commit](https://img.shields.io/github/last-commit/d-markey/squadron?logo=git\u0026logoColor=white)](https://github.com/d-markey/squadron/commits)\n[![Dart Workflow](https://github.com/d-markey/squadron/actions/workflows/dart.yml/badge.svg?logo=dart)](https://github.com/d-markey/squadron/actions/workflows/dart.yml)\n[![Code Lines](https://img.shields.io/badge/dynamic/json?color=blue\u0026label=code%20lines\u0026query=%24.linesValid\u0026url=https%3A%2F%2Fraw.githubusercontent.com%2Fd-markey%2Fsquadron%2Fmain%2Fcoverage.json)](https://github.com/d-markey/squadron/tree/main/coverage/html)\n[![Code Coverage](https://img.shields.io/badge/dynamic/json?color=blue\u0026label=code%20coverage\u0026query=%24.lineRate\u0026suffix=%25\u0026url=https%3A%2F%2Fraw.githubusercontent.com%2Fd-markey%2Fsquadron%2Fmain%2Fcoverage.json)](https://github.com/d-markey/squadron/tree/main/coverage/html)\n\n\u003c/td\u003e\n\u003c/tr\u003e\u003c/table\u003e\n\n[View latest documentation on GitHub](https://github.com/d-markey/squadron/blob/main/README.md)\n\n[Test Console available on GitHub Pages](https://d-markey.github.io/squadron/test-console/)\n\n## \u003ca name=\"getting_started\"\u003e\u003c/a\u003eGetting Started\n\n1. Update your `pubspec.yaml` file to add dependencies to **[Squadron][pub_squadron]** and **[squadron_builder][pub_squadron_builder]** + [build_runner](https://pub.dev/packages/build_runner):\n\n```yaml\ndependencies:\n  squadron: ^7.1.0\n  # ...\n\ndev_dependencies:\n  build_runner:\n  squadron_builder: ^7.1.0\n  # ...\n```\n\n2. Have dart download and install the dependencies:\n\n```bash\ndart pub get\n```\n\n## \u003ca name=\"implement\"\u003e\u003c/a\u003eImplementing a Service\n\nCreate a class containing the code you intend to run in a dedicated thread and make sure you provide `squadron` annotations:\n\n* use **`SquadronService`** for the class;\n\n* use **`SquadronMethod`** for the methods you want to expose.\n\nService methods must return a `Future\u003cT\u003e`, a `FutureOr\u003cT\u003e`, or a `Stream\u003cT\u003e`.\n\n```dart\n// file hello_world.dart\nimport 'dart:async';\n\nimport 'package:squadron/squadron.dart';\n\nimport 'hello_world.activator.g.dart';\npart 'hello_world.worker.g.dart';\n\n@SquadronService(baseUrl: '~/workers', targetPlatform: TargetPlatform.vm | TargetPlatform.web)\n// or @SquadronService(baseUrl: '~/workers', targetPlatform: TargetPlatform.all)\nbase class HelloWorld {\n  @SquadronMethod()\n  FutureOr\u003cString\u003e hello([String? name]) {\n    name = name?.trim() ?? 'World';\n    return 'Hello, $name!';\n  }\n}\n```\n\n## \u003ca name=\"build\"\u003e\u003c/a\u003eGenerating the Worker and WorkerPool code\n\nHave [squadron_builder][pub_squadron_builder] generate the code with the following command line:\n\n```bash\ndart run build_runner build\n```\n\nThis command will create the worker and worker pool from your service: `HelloWorldWorker` and `HelloWorldWorkerPool`.\n\nWorkers and worker pools generated by [squadron_builder][pub_squadron_builder] implement the same interface as the original service and proxy all method calls to an instance of the service running in its own thread.\n\n## \u003ca name=\"run\"\u003e\u003c/a\u003eSpawning a Worker\n\nIn your program, you can instantiate a `Worker` (or a `WorkerPool` if you need more threads) and use it just as you would use your original service.\n\n**Make sure you stop the workers and pools before exiting** your program. Failure to do so will let your program run forever.\n\n```dart\n// file main.dart\nimport 'package:squadron/squadron.dart';\n\nimport 'hello_world.dart';\n\nvoid main() async {\n  final worker = HelloWorldWorker();\n  try {\n    // Squadron will start the worker for you so you don't have to call worker.start()\n    final message = await worker.hello();\n    print(message);\n  } finally {\n    // make sure the worker is stopped when the program terminates\n    worker.stop();\n  }\n}\n```\n\n## \u003ca name=\"web\"\u003e\u003c/a\u003eBuilding for the Web\n\n**You must compile your code to JavaScript or Web Assembly** if your app is designed to run in a browser.\n\n```bash\ndart compile js \".\\src\\lib\\hello_world.web.g.dart\" -o \"..\\web\\workers\\hello_world.web.g.dart.js\"\ndart compile wasm \".\\src\\lib\\hello_world.web.g.dart\" -o \"..\\web\\workers\\hello_world.web.g.dart.wasm\"\n```\nWhen 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`.\n\nYou 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` or shortcut `TargetPlatform.web`.\n\n## \u003ca name=\"constraints\"\u003e\u003c/a\u003eMultithreading Constraints\n\nThere are a few constraints to multithreading in Dart:\n\n* **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.\n\n* **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 recover strongly-typed data on the receiving-end.\n\nData sent through Squadron are handled as `dynamic` types: to recover strong types and guarantee type-safety in your code, Squadron provides `Converter`s to \"convert\" data on the receiving-end:\n\n* native platforms use a `CastConverter` that will try its best to just cast data and avoid inspecting collection contents. This may not always be possible and it could impact performance.\n\n* on Web platforms, objects sent to/from a Web Worker leave Dart's realm when they go through the browser's `postMessage()` function, losing their Dart type in the process. They must therefore re-enter Dart's type-system on the receiving end. Squadron provides a `CastConverter` (converting data as well as items in `List`/`Set`/`Map` objects) and a `NumConverter` (adding special handling for `int`/`double` values) depending on the underlying runtime (JavaScript or Web Assembly).\n\n### \u003ca name=\"types_native\"\u003e\u003c/a\u003eNative Platforms\n\nOn 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 object types are retained.\n\nThere are a few constraints on what type of data can be transferred, please refer to [SendPort.send()](https://api.dart.dev/dart-isolate/SendPort/send.html) documentation for more information.\n\nOn native platforms, Squadron uses a default `CastConverter` that will try to simply cast data on the receiving end.\n\nHowever, when there are nested conversions (eg. `list\u003cT\u003e(nestedConversion)`), casting is only possible when the nested conversion is the identity (`(x) =\u003e x as T`). In other situations, the conversion must be applied and this could impact performance in complex data structures (such as maps / lists). See [Optimizing Conversions](#optimizing-conversions) for more information.\n\n### \u003ca name=\"types_web\"\u003e\u003c/a\u003eWeb Platforms\n\nWeb platforms have stronger constraints when it comes to transferable objects: for more information, please refer to [Transferable objects](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects) documentation or the [HTML specification for transferable objects](https://html.spec.whatwg.org/multipage/structured-data.html#transferable-objects). There may also be differences between browser flavors and versions.\n\nOn Web plaforms, Squadron uses a default `CastConverter` (JavaScript runtime) or `NumConverter` (Web Assembly runtime). One of the key differences between Dart, Web Assembly and JavaScript is number handling: JavaScript only really knows `double` numbers whereas Dart and Web Assembly support `int` and `double` as different types. As a result, on JavaScript platforms, [Dart's `int` is actually a subtype of `double`](https://dart.dev/guides/language/numbers#types-and-type-checking) and special care is required when transfering numbers: on Web Assembly platforms, the receiving-end may receive `int` values as `double` and require a conversion back to `int`.\n\nMore 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](https://pub.dev/documentation/squadron/latest/squadron/SquadronMarshaler-class.html).\n\nHere's an example of a `Person` class, and a Squadron `PersonMarshaler` to serialize/deserialize instances of `Person`:\n\n```dart\nclass Person {\n  Person(this.firstName, this.lastName, this.dateOfBirth);\n\n  String firstName;\n  String lastName;\n  final DateTime dateOfBirth;\n}\n\nclass PersonMarshaler implements GenericMarshaler\u003cPerson\u003e {\n  const PersonMarshaler();\n\n  @override\n  dynamic marshal(Person data, [MarshalingContext? context]) =\u003e [\n    data.firstName,\n    data.lastName,\n    data.dateOfBirth.millisecondsSinceEpoch,\n  ];\n\n  @override\n  Person unmarshal(dynamic data, [MarshalingContext? context]) {\n    data as List;\n    // while the entry produced by `marshal` is an `int`,\n    // it could require conversion e.g. with Wasm workers\n    // where `int` values are actually transferred as `double`\n    final dob = context.converter.value\u003cint\u003e()(data[2]);\n    return Person(data[0], data[1], dob);\n  }\n} \n```\n\n[squadron_builder][pub_squadron_builder] implements proper conversion in and out when generating the code for `PersonServiceWorker`. You only need to apply the marshaler by annotating `Person` parameters and/or return values:\n\n```dart\n@SquadronService(baseUrl: '~/workers', targetPlatform: TargetPlatform.web)\nbase class PersonService {\n  @SquadronMethod()\n  FutureOr\u003cdouble\u003e getAge(@PersonMarshaler() Person p) {\n    final now = DateTime.now(), dob = p.dateOfBirth;\n    final today = DateTime(now.year, now.month, now.day);\n    DateTime lastBirthday, nextBirthday;\n    lastBirthday = DateTime(today.year, dob.month, dob.day);\n    if (lastBirthday.isAfter(today)) {\n      nextBirthday = lastBirthday;\n      lastBirthday = DateTime(nextBirthday.year - 1, dob.month, dob.day);\n    } else {\n      nextBirthday = DateTime(lastBirthday.year + 1, dob.month, dob.day);\n    }\n    final years = lastBirthday.year - dob.year;\n    final sinceLast = today.difference(lastBirthday).inDays;\n    final toNext = nextBirthday.difference(lastBirthday).inDays;\n    return years + sinceLast / toNext;\n  }\n}\n```\n\nEven easier, just annotate the `Person` class with the marshaler!\n\n```dart\n@PersonMarshaler()\nclass Person {\n  // ...\n}\n```\n\n### \u003ca name=\"conversions\"\u003e\u003c/a\u003eOptimizing Conversions\n\nAs stated in a previous paragraph, code designed to run only on native platforms should not worry about data conversion. Because Squadron native workers share the same code and execute in `Isolate`s running in the same Dart VM, they never leave Dart's type-system. Data sent through Squadron is promoted from `dynamic` back to strong-types by simple cast operations.\n\nOn Web platforms, things are different because the data was handed over to the browser which doesn't know anything about Dart types:\n\n* `bool` and `String`: casting is enough to re-enter Dart's type system (handled by `CastConverter`).\n\n* `int` and `double`: integers may be received as floating points numbers; in JavaScript runtimes, `int` is a subtype of `double` and casting is enough (handled by `CastConverter`); in Web Assembly runtimes, integer values may be received as a `double` and require conversion back to `int` (handled by `NumConverter`).\n\n* `List`, `Set` and `Map`: these objects are received as `List\u003cdynamic\u003e`, `Set\u003cdynamic\u003e` and `Map\u003cdynamic, dynamic\u003e`. Item, key and value types are systematically lost. Type-casting is not enough and would always fail with a `TypeError`. Additional processing is required from the converter.\n\nTo handle collections as efficiencly as possible, converters provided by Squadron optimize the process when the item type is a base type that can be handled by a simple cast. Eg. when a service method works with a `List\u003cString\u003e`, it is received as a `List\u003cdynamic\u003e` and will be \"promoted\" back to `List\u003cString\u003e` by simply calling `list.cast\u003cString\u003e()` (same for `Set`). For `Map\u003cK, V\u003e` objects where `K` and `V` are base types, the received `Map\u003cdynamic, dynamic\u003e` will be cast back to `Map\u003cK, V\u003e` with `map.cast\u003cK, V\u003e()`. In these scenarios, cast operations are deferred until items are accessed. Dart's static type-safety checks should guarantee success of cast operations.\n\nWhen collections contain elements that cannot be cast, additional processing is required.\n\nWeb Assembly workers requires extra-care: starting from version 7.0, Squadron has implemented custom versions of `package:web`'s `jsify` and `dartify` functions to better work with Squadron's `NumConverter`. This implementation helps handle the `int`/`double` issue (see [issue #55203](https://github.com/dart-lang/sdk/issues/55203)) as well as the stringified-keys issue in `maps` (see [issue #57113](https://github.com/dart-lang/sdk/issues/57113)).\n\nWhile casting should be enough (and hopefully not too expensive) on VM platform, it will not be the case on Web platforms where performance might be impacted. For instance, when working with large collections or complex structures such as nested lists/maps, the conversion process may require inspecting all list items or map keys \u0026 values. It can be optimized in various ways, eg. a large list of integers could be handled via a `Int32List` that will travel to/from workers via a (hopefully efficient) byte buffer.\n\n### \u003ca name=\"marshaling\"\u003e\u003c/a\u003eMarshaling\n\nConverters only take care of base types (strings, numbers, booleans, lists, maps and sets as well as Dart's typed data and `BigInt`). The default behavior for other types is to simply cast the `dynamic` value to the specified type, whether they're Dart types such as `DateTime` or `Duration`, or custom types that you or a third-party package implemented.\n\nBut casting will only work on native Dart VM. On browser platforms, custom objects must be serialized when sent and deserialized when received. Squadron provides `SquadronMarshaler\u003cT, S\u003e` for you to implement your own marshaler:\n\n* `S marshal(T data, [MarshalingContext? context])`: implement this method to serialize an instance of `T` to something that can be transfered, for instance a `List`;\n\n* `T unmarshal(S data, [MarshalingContext? context])`: implement this method to deserialize from `S` and back to `T`.\n\n`unmarshal(marchal(obj))` should produce an instance of `T` that is functionaly equivalent to the original instance `obj`.\n\nFor instance, given the following class:\n\n```dart\nclass Car {\n  Car(this.color, this.price, this.engine);\n\n  final String color;\n  final double price;\n  final Engine engine;\n}\n\nenum Engine { gaz, diesel, electric }\n```\n\nA marshaler could be implemented as:\n\n```dart\n// for use as an annotation\nconst carMarshaler = CarMarshaler();\n\nclass CarMarshaler implements SquadronMarshaler\u003cCar, List\u003e {\n  const CarMarshaler();\n\n  List marshal(Car data, [MarshalingContext? context]) =\u003e [\n      data.color, // color at index 0\n      data.price, // price at index 1\n      data.engine.index, // engine at index 2\n    ];\n\n  Car unmarshal(List data, [MarshalingContext? context]) {\n    final price = context.converter.value\u003cdouble\u003e()(data[1]);\n    final engineIndex = context.converter.value\u003cint\u003e()(data[2]);\n    return Car(\n      data[0],\n      price,\n      Engine.values[engineIndex]),\n    );\n  }\n}\n```\n\n[squadron_builder][pub_squadron_builder] generates code using the marshaler annotation you provide in your service implementation:\n\n```dart\n@SquadronService()\nclass CarService {\n  @serviceMethod\n  @carMarshaler\n  FutureOr\u003cCar?\u003e buy(double cash, String color) { /* ... */ }\n\n  @serviceMethod\n  FutureOr\u003cdouble\u003e sell(@carMarshaler Car car) { /* ... */ }\n}\n```\n\nAlternatively, if you own the target class, you can also simply annotate it:\n\n```dart\n@carMarshaler\nclass Car {\n  // ...\n}\n\n@SquadronService()\nclass CarService {\n  @serviceMethod\n  FutureOr\u003cCar\u003e buy(double cash, String color) { /* ... */ }\n\n  @serviceMethod\n  FutureOr\u003cdouble\u003e sell(Car car) { /* ... */ }\n}\n```\n\nIf your application is designed to run both on native and Web platforms, it is possible to optimize for VM platforms by providing different marshalers depending on the platform and conditionally import the proper implementation. Eg. on VM platforms, marshalers would mostly be 'no-ops'.\n\n```dart\n///////////// file car_marshaler.web.dart /////////////\n// for use as an annotation\nconst carMarshaler = _CarMarshaler();\n\nclass _CarMarshaler implements SquadronMarshaler\u003cCar, List\u003e {\n  const CarMarshaler();\n\n  List marshal(Car data, [MarshalingContext? context]) =\u003e [ /* fields */ ];\n\n  Car unmarshal(List data, [MarshalingContext? context]) =\u003e Car(/* arguments */);\n}\n\n\n///////////// file car_marshaler.vm.dart /////////////\n\n// for use as an annotation\n// IdentityMarshalers do nothing :)\nconst carMarshaler = IdentityMarshaler\u003cCar\u003e();\n\n\n///////////// file car.dart /////////////\n\nimport 'car_marshaler.web.dart'\n  if (dart.library.io) 'car_marshaler.vm.dart';\n\n@carMarshaler\nclass Car {\n  // ...\n}\n```\n\n### \u003ca name=\"marshaling-context\"\u003e\u003c/a\u003eMarshaling Context\n\nMarshalers can work with a `MarshalingContext`. The marshaling context stores results of marshaling/unmarshaling operations and serves the same result if the same instance is un/marshaled again.\n\n**Using a marshaling context guarantees instance identities after transfer.** For instance:\n\n```dart\nclass Person {\n  Person(this.name, [this.father, this.mother]);\n\n  final String name;\n  final Person? father;\n  final Person? mother;\n}\n\nPerson father = Person('Dad'), mother = Person('Mom');\nPerson me = Person('Me', father, mother);\nPerson sister = Person('Sister', father, mother);\nPerson brother = Person('Brother', father, mother);\n```\n\nWithout a marshaling context, sending `me` and `brother` to a Web worker, will result in different instances of `father` and `mother` after un/marshaling. As a result, `identical(me.father, brother.father)` holds true on the sender side, but false on the receiving end. Using a marshaling context, `me.father` and `brother.father` will result in the same instance after un/marshaling.\n\n**Marshaling contexts also allow for cyclical references.** For instance, adding a `children` property to the `Person` class:\n\n```dart\nclass Person {\n  /// ...\n\n  final children = \u003cPerson\u003e[];\n}\n\n// ...\nfather.children.addAll([me, brother, sister]);\nmother.children.addAll([me, brother, sister]);\n```\n\nThis creates cyclical references that will fail to marshal unless a marshaling context is used.\n\nAgain, Squadron is not opinionated when it comes to serialization: it will try and support you in as many ways as possible. You keep full control over the way you choose to serialize your custom classes. For instance, you may choose to not send children as full `Person` instances and instead send just some ids or a subset of properties that won't introduce circular dependencies. It's up to you to assess pros and cons and implement your own solution.\n\nFor a concrete example, see https://github.com/d-markey/squadron/blob/main/test/worker_services/persons.\n\n## \u003ca name=\"exceptions\"\u003e\u003c/a\u003eExceptions\n\nExceptions thrown form a `Worker` also need to cross thread-boundaries.\n\nYou can implement custom exceptions by deriving from `WorkerException`.\n\n* Custom exception classes must  override the `serialize()` method to return an array with the exception's properties. The first item must be a code of your choice (refered to as the \"Exception Type ID\").\n* A static function to deserialize this array and recreate the custom exception will also be required.\n* Finally, an `ExceptionManager` must be provided to `Worker`s and `WorkerPool`s. The custom exception deserialization function must be registered with the `ExceptionManager` and associated to the \"Exception Type ID\".\n\nIf an exception occurs in the worker code (\"background thread\" or callee side), Squadron will serialize it and revive it on the caller side (the \"main thread\").\n\n## \u003ca name=\"logging\"\u003e\u003c/a\u003eLogging\n\nLogging is easy: just use Dart's `print()` function! There's one caveat when debugging your code in the browser: Web workers are not attached to Dart's debugger, so log messages will end-up in the browser's console.\n\nIt is possible to implement a logger and have log messages get redirected to the main thread, which is attached to Dart's debugger. This can be done by using a local worker. For more information, please refer to the `local_logger` example.\n\n## \u003ca name=\"thanks\"\u003e\u003c/a\u003eThanks!\n\n* [Saad Ardati](https://github.com/SaadArdati) for his patience and feedback when implementing Squadron into his Flutter application.\n* [Martin Fink](https://github.com/martin-robert-fink) for the feedback on Squadron's first `Stream` implementation -- this has resulted in huge progress and a major improvement.\n* [Klemen Tusar](https://github.com/techouse) for providing a [sample Chopper JSON decoder](https://hadrien-lejard.gitbook.io/chopper/faq#decoding-json-using-isolates) leveraging Squadron.\n* [James O'Leary](https://github.com/jpohhhh) for sponsorship and contribution, very much appreciated.\n\n\n\n[pub_squadron]: https://pub.dev/packages/squadron\n[pub_squadron_builder]: https://pub.dev/packages/squadron_builder\n","funding_links":["https://github.com/sponsors/d-markey"],"categories":["Dart"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd-markey%2Fsquadron","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fd-markey%2Fsquadron","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd-markey%2Fsquadron/lists"}