runAsync<P, R> method

Future<R> runAsync<P, R>(
  1. RunAsyncCallback<P, R> callback,
  2. P param
)

Spawns an isolate, runs callback in that isolate passing it param with its own Store and returns the result of callback.

This is useful for ObjectBox operations that take longer than a few milliseconds, e.g. putting many objects, which would cause frame drops. If all operations can execute within a single transaction, prefer to use runInTransactionAsync.

The Store given to the callback does not have to be closed, it is closed by the worker isolate once the callback returns (or throws).

The following example gets the name of a User object, deletes the object and returns the name:

String? readNameAndRemove(Store store, int objectId) {
  var box = store.box<User>();
  final nameOrNull = box.get(objectId)?.name;
  box.remove(objectId);
  return nameOrNull;
}
await store.runAsync(readNameAndRemove, objectId);

The callback must be a function that can be sent to an isolate: either a top-level function, static method or a closure that only captures objects that can be sent to an isolate.

Warning: Due to dart-lang/sdk#36983 a closure may capture more objects than expected, even if they are not directly used in the closure itself.

The types P (type of the parameter to be passed to the callback) and R (type of the result returned by the callback) must be able to be sent to or received from an isolate. The same applies to errors originating from the callback.

See SendPort.send for a discussion on which values can be sent to and received from isolates.

Note: this requires Dart 2.15.0 or newer (shipped with Flutter 2.8.0 or newer).

Implementation

Future<R> runAsync<P, R>(RunAsyncCallback<P, R> callback, P param) async {
  final port = RawReceivePort();
  final completer = Completer<dynamic>();

  void _cleanup() {
    port.close();
  }

  port.handler = (dynamic message) {
    _cleanup();
    completer.complete(message);
  };

  final Isolate isolate;
  try {
    // Await isolate spawn to avoid waiting forever if it fails to spawn.
    isolate = await Isolate.spawn(
        _callFunctionWithStoreInIsolate,
        _RunAsyncIsolateConfig(_modelDefinition, directoryPath,
            _queriesCaseSensitiveDefault, port.sendPort, callback, param),
        errorsAreFatal: true,
        onError: port.sendPort,
        onExit: port.sendPort);
  } on Object {
    _cleanup();
    rethrow;
  }

  final dynamic response = await completer.future;
  // Replace with Isolate.exit in _callFunctionWithStoreInIsolate
  // once min SDK 2.15.
  isolate.kill();

  if (response == null) {
    throw RemoteError('Isolate exited without result or error.', '');
  }

  if (response is _RunAsyncResult) {
    // Success, return result.
    return response.result as R;
  } else if (response is List<dynamic>) {
    // See isolate.addErrorListener docs for message structure.
    assert(response.length == 2);
    await Future<Never>.error(RemoteError(
      response[0] as String,
      response[1] as String,
    ));
  } else {
    // Error thrown by callback.
    assert(response is _RunAsyncError);
    response as _RunAsyncError;

    await Future<Never>.error(
      response.error,
      response.stack,
    );
  }
}