Type conversion error when trying to use Isolate.spawn()

Issue

I’m having a problem with Dart’s generic types when using Isolate.spawn(). I feel this should work, but it doesn’t.

I’m trying to write a type-safe(-ish) wrapper around Isolate.spawn() that will ensure I’m passing valid types into the function I’d like to run in another thread (input arguments), and type of the result value I’m getting from this function (output result value).

So, I create the InputType and OutputType dummy classes that act as my input and output types. The thread function is a function that I’d like to run in another thread. The run function is the actual wrapper: it should accept the function to run in another thread, and it’s argument.

import 'dart:async';
import 'dart:isolate';

typedef Callback<I, R> = Future<R> Function(I input);

class Config<I, R> {
  final Callback<I, R> callback;
  final I arg;
  final SendPort port;
  Config(this.callback, this.arg, this.port);
}

class InputType {
  int arg;
  InputType(this.arg);
}

class OutputType {
  String str;
  OutputType(this.str);
}

Future<R> _spawn<I, R>(Config<I, R> conf) async {
  print("callback: ${conf.callback}");
  return await conf.callback(conf.arg);
}

void run<I, R>(Callback<I, R> func, I arg) async {
  ReceivePort resultPort = ReceivePort();

  Config<I, R> conf = Config<I, R>(func, arg, resultPort.sendPort);
  Isolate thread = await Isolate.spawn<Config<I, R>>(_spawn, conf);
  // ...
}

Future<OutputType> thread(InputType input) async {
  print("running in isolate");
  return OutputType("Hello, arg was: ${input.arg}");
}

void main() async {
  print("runtime");
  run<InputType, OutputType>(thread, InputType(123));
}

The resultig error I’m having:

$ dart isolate.dart                                                                                  
runtime
Unhandled exception:
type '(InputType) => Future<OutputType>' is not a subtype of type '(dynamic) => Future<dynamic>'
#0      _spawn (file:///home/antek/dev/dart/tests/generic/isolate.dart:24:27)
#1      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:286:17)
#2      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
^C

The error actually comes from this line:

print("callback: ${conf.callback}");

Does anyone have an idea how to fix the problem?

Solution

Dart hackity-hack to the rescue.

It seems that when passing the reference to _spawn function to Isolate.spawn(), the type information is being lost.

A fix that I’ve found is to add a helper function inside the Config<I, R> class that calls the callback. The instance of Config<I, R> seems to have all the necessary information about the types. So, after I’ve modified my example above like this:

class Config<I, R> {
  final Callback<I, R> callback;
  final I arg;
  final SendPort port;
  Config(this.callback, this.arg, this.port);

  Future<R> call() async {
    return await callback(arg);
  }
}

// ...

Future<R> _spawn<I, R>(Config<I, R> conf) async {
  var result = await conf.call();
  return result;
}

it started to work correctly.

Full example source with necessary blocking and one-way communication:

import 'dart:async';
import 'dart:isolate';

typedef Callback<I, R> = Future<R> Function(I input);

class Config<I, R> {
  final Callback<I, R> callback;
  final I arg;
  final SendPort port;
  Config(this.callback, this.arg, this.port);

  Future<R> call() async {
    return await callback(arg);
  }
}

class InputType {
  int arg;
  InputType(this.arg);
}

class OutputType {
  String str;
  OutputType(this.str);
}

Future<R> _spawn<I, R>(Config<I, R> conf) async {
  var result = await conf.call();
  conf.port.send(result);
  return result;
}

Future<R> run<I, R>(Callback<I, R> func, I arg) async {
  ReceivePort resultPort = ReceivePort();

  Config<I, R> conf = Config<I, R>(func, arg, resultPort.sendPort);
  Future<Isolate> thread = Isolate.spawn<Config<I, R>>(_spawn, conf);

  var c = Completer<R>();
  resultPort.listen((data) {
    c.complete(data as R);
  });

  await c.future;

  resultPort.close();
  (await thread).kill();
  return c.future;
}

Future<OutputType> thread(InputType input) async {
  return OutputType("Hello, arg was: ${input.arg}");
}

void main() async {
  var result = await run<InputType, OutputType>(thread, InputType(123));
  print("main() result: ${result.str}");
}

Answered By – antonone

Answer Checked By – Timothy Miller (FlutterFixes Admin)

Leave a Reply

Your email address will not be published.