run<R> static method Null safety

  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.

    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;
    }