run<R> static method
- @Since("2.19")
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;
}