run<R> static method

  1. @Since("2.19")
Future<R> run<R>(
  1. FutureOr<R> computation(
      ),
    1. {String? debugName}
    )

    Runs computation in a new isolate and returns the result.

    int slowFib(int n) =>
        n <= 1 ? 1 : slowFib(n - 1) + slowFib(n - 2);
    
    // Compute without blocking current isolate.
    var fib40 = await Isolate.run(() => slowFib(40));
    

    If computation is asynchronous (returns a Future<R>) then that future is awaited in the new isolate, completing the entire asynchronous computation, before returning the result.

    int slowFib(int n) =>
        n <= 1 ? 1 : slowFib(n - 1) + slowFib(n - 2);
    Stream<int> fibStream() async* {
      for (var i = 0;; i++) yield slowFib(i);
    }
    
    // Returns `Future<int>`.
    var fib40 = await Isolate.run(() => fibStream().elementAt(40));
    

    If computation throws, the isolate is terminated and this function throws the same error.

    Future<int> eventualError() async {
      await Future.delayed(const Duration(seconds: 1));
      throw StateError("In a bad state!");
    }
    
    try {
      await Isolate.run(eventualError);
    } on StateError catch (e, s) {
      print(e.message); // In a bad state!
      print(LineSplitter.split("$s").first); // Contains "eventualError"
    }
    

    Any uncaught asynchronous errors will terminate the computation as well, but will be reported as a RemoteError because addErrorListener does not provide the original error object.

    The result is sent using exit, which means it's sent to this isolate without copying.

    The computation function and its result (or error) must be sendable between isolates. Objects that cannot be sent include open files and sockets (see SendPort.send for details).

    If computation is a closure then it may implicitly send unexpected state to the isolate due to limitations in the Dart implementation. This can cause performance issues, increased memory usage (see http://dartbug.com/36983) or, if the state includes objects that can't be spent between isolates, a runtime failure.

    
    void serializeAndWrite(File f, Object o) async {
      final openFile = await f.open(mode: FileMode.append);
      Future writeNew() async {
        // Will fail with:
        // "Invalid argument(s): Illegal argument in isolate message"
        // because `openFile` is captured.
        final encoded = await Isolate.run(() => jsonEncode(o));
        await openFile.writeString(encoded);
        await openFile.flush();
        await openFile.close();
      }
    
      if (await openFile.position() == 0) {
        await writeNew();
      }
    }
    

    In such cases, you can create a new function to call Isolate.run that takes all of the required state as arguments.

    
    void serializeAndWrite(File f, Object o) async {
      final openFile = await f.open(mode: FileMode.append);
      Future writeNew() async {
        Future<String> encode(o) => Isolate.run(() => jsonEncode(o));
        final encoded = await encode(o);
        await openFile.writeString(encoded);
        await openFile.flush();
        await openFile.close();
      }
    
      if (await openFile.position() == 0) {
        await writeNew();
      }
    }
    

    The debugName is only used to name the new isolate for debugging.

    Implementation

    @Since("2.19")
    static Future<R> run<R>(FutureOr<R> computation(), {String? debugName}) {
      var result = Completer<R>();
      var resultPort = RawReceivePort();
      resultPort.handler = (response) {
        resultPort.close();
        if (response == null) {
          // onExit handler message, isolate terminated without sending result.
          result.completeError(
              RemoteError("Computation ended without result", ""),
              StackTrace.empty);
          return;
        }
        var list = response as List<Object?>;
        if (list.length == 2) {
          var remoteError = list[0];
          var remoteStack = list[1];
          if (remoteStack is StackTrace) {
            // Typed error.
            result.completeError(remoteError!, remoteStack);
          } else {
            // onError handler message, uncaught async error.
            // Both values are strings, so calling `toString` is efficient.
            var error =
                RemoteError(remoteError.toString(), remoteStack.toString());
            result.completeError(error, error.stackTrace);
          }
        } else {
          assert(list.length == 1);
          result.complete(list[0] as R);
        }
      };
      try {
        Isolate.spawn(_RemoteRunner._remoteExecute,
                _RemoteRunner<R>(computation, resultPort.sendPort),
                onError: resultPort.sendPort,
                onExit: resultPort.sendPort,
                errorsAreFatal: true,
                debugName: debugName)
            .then<void>((_) {}, onError: (error, stack) {
          // Sending the computation failed asynchronously.
          // Do not expect a response, report the error asynchronously.
          resultPort.close();
          result.completeError(error, stack);
        });
      } on Object {
        // Sending the computation failed synchronously.
        // This is not expected to happen, but if it does,
        // the synchronous error is respected and rethrown synchronously.
        resultPort.close();
        rethrow;
      }
      return result.future;
    }