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?
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}");
}