isolate应用和封装

·  阅读 367

上一章,分享了Dart异步和isolate的基本知识。这一章,分享isolate的应用。

创建Isolate

基本方法 Flutter应用中的代码默认都跑在root isloate 中,尽管是单线程但已经足够处理各类异步任务。

当有计算密集型的耗时任务时,就需要创建新的Isolate来进行耗时计算来避免阻塞root isloate。由于不同Isolate之间内存隔离,要通信就得通过 ReceivePort与SendPort 来实现。

使用Isolate.spawn来创建新的Isolate。看下函数签名

external static Future<Isolate> spawn<T>(
  void entryPoint(T message), T message,
  {bool paused = false,
  bool errorsAreFatal = true,
  SendPort? onExit,
  SendPort? onError,
  @Since("2.3") String? debugName});
复制代码

创建新的Isolate最主要的就以下两步:

1、使用顶层函数或静态方法定义计算任务

// 耗时计算部分
int fibonacci(int n) {
  return n < 2 ? n : fibonacci(n - 2) + fibonacci(n - 1);
}

// 1. 计算任务
void task1(int start) {
  DateTime startTime = DateTime.now();
  int result = fibonacci(start);
  DateTime endTime = DateTime.now();
  print("计算耗时:${endTime.difference(startTime)}  结果:${result.toString()}");
}

void main() {
  task1(50);
}

// 输出:计算耗时:0:00:48.608656  结果:12586269025
复制代码

上面的计算要耗时48秒,必须要在新的Isolate中做计算。

最简单的 compute

通常网络返回 json ,我们需要解析成 实体 bean ,如果 json 十分庞大,耗时较多,就卡顿了。所以需要放在isolate里处理。

import 'dart:convert';

main(List<String> args) {
  String jsonString = '''{ "id":"123", "name":"张三", "score" : 95}''';
  Student student = parseJson(jsonString);
  print(student.name);
}

Student parseJson(String json) {
  Map<String, dynamic> map = jsonDecode(json);
  return Student.fromJson(map);
}

class Student {
  String id;
  String name;
  int score;
  Student({this.id, this.name, this.score});
  factory Student.fromJson(Map parsedJson) {
    return Student(id: parsedJson['id'], name: parsedJson['name'], score: parsedJson['score']);
  }
}
复制代码

我们把上面代码放入isolate中执行:

Future<Student> loadStudent(String json) {
  return compute(parseJson, json);
}

Student parseJson(String json) {
  Map<String, dynamic> map = jsonDecode(json);
  return Student.fromJson(map);
}
复制代码

compute是 Flutter 的 api ,帮我们封装了 isolate,使用十分简单,但是也有局限性,它没有办法多次返回结果,也没有办法持续性的传值计算,每次调用,相当于新建一个隔离,如果同时调用过多的话反而会多次开辟内存。在某些业务下,我们可以使用compute,但是在另外一些业务下,我们只能使用dart提供的 isolate了。

单向通信

定义可以接收宿主的SendPort的耗时任务,在计算完成后通过send方法将结果发回宿主Isolate。

我们把上面的代码利用isolate实现一遍:

import 'dart:convert';
import 'dart:isolate';

main(List<String> args) async {
  await start();
}

Isolate isolate;

start() async {
  //创建接收端口,用来接收子线程消息
  ReceivePort receivePort = ReceivePort();

  //创建并发Isolate,并传入主线程发送端口
  isolate = await Isolate.spawn(entryPoint, receivePort.sendPort);
  //监听子线程消息
  receivePort.listen((data) {
    print('Data:$data');
  });
}

//并发Isolate
entryPoint(SendPort sendPort) {
  String jsonString = '''{ "id":"123", "name":"张三", "score" : 95}''';
  Student student = parseJson(jsonString);
  sendPort.send(student);
}

Student parseJson(String json) {
  Map<String, dynamic> map = jsonDecode(json);
  return Student.fromJson(map);
}

class Student {
  String id;
  String name;
  int score;
  Student({this.id, this.name, this.score});
  factory Student.fromJson(Map parsedJson) {
    return Student(id: parsedJson['id'], name: parsedJson['name'], score: parsedJson['score']);
  }
}
复制代码
双向通信

子Isolate向宿主Isolate发送数据是通过,持有宿主中的SendPort来实现的。

通过上面的改造,将宿主中的SendPort传递给子Isolate,在计算出结果后发回给宿主Isolate,宿主Isolate通过ReceivePort设置监听来处理结果。就完成了子Isolate向宿主Isolate发送数据。

但宿主Isolate如何向子Isolate发送数据呢?

子Isolate向宿主Isolate发送数据是通过,持有宿主中的SendPort来实现的。那么要实现宿主Isolate向子Isolate发送数据,宿主中也得持有子Isolate中的SendPort才行。最简单的方案就是在子Isolate中创建subSendPort并传递回宿主。

import 'dart:isolate';

main(List<String> args) async {
  await start();
  await Future.delayed(Duration(seconds: 1), () {
    threadPort.send('我来自主线程');
    print('1');
  });
  await Future.delayed(Duration(seconds: 1), () {
    threadPort.send('我也来自主线程');
    print('2');
  });
  await Future.delayed(Duration(seconds: 1), () {
    threadPort.send('end');
    print('3');
  });
}

Isolate isolate;
//子线程发送端口
SendPort threadPort;
start() async {
  //创建主线程接收端口,用来接收子线程消息
  ReceivePort receivePort = ReceivePort();

  //创建并发Isolate,并传入主线程发送端口
  isolate = await Isolate.spawn(entryPoint, receivePort.sendPort);
  //监听子线程消息
  receivePort.listen((data) {
    print('主线程收到来自子线程的消息$data');
    if (data is SendPort) {
      threadPort = data;
    }
  });
}

//并发Isolate
entryPoint(dynamic message) {
  //创建子线程接收端口,用来接收主线程消息
  ReceivePort receivePort = ReceivePort();

  SendPort sendPort;
  print('==entryPoint==$message');
  if (message is SendPort) {
    sendPort = message;
    print('子线程开启');
    sendPort.send(receivePort.sendPort);
    //监听子线程消息
    receivePort.listen((data) {
      print('子线程收到来自主线程的消息$data');
      assert(data is String);
      if (data == 'end') {
        isolate?.kill();
        isolate = null;
        print('子线程结束');
        return;
      }
    });
    return;
  }
}
复制代码

最终输出结果为:

==entryPoint==SendPort
子线程开启
主线程收到来自子线程的消息SendPort
1
子线程收到来自主线程的消息我来自主线程
2
子线程收到来自主线程的消息我也来自主线程
3
子线程收到来自主线程的消息end
子线程结束
复制代码

双向通信比较复杂,所以我们需要封装下,通过 api 让外部调用:

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

main(List<String> args) async {
  var worker = Worker();
  worker.reuqest('发送消息1').then((data) {
    print('子线程处理后的消息:$data');
  });

  Future.delayed(Duration(seconds: 2), () {
    worker.reuqest('发送消息2').then((data) {
      print('子线程处理后的消息:$data');
    });
  });
}

class Worker {
  SendPort _sendPort;
  Isolate _isolate;
  final _isolateReady = Completer<void>();
  final Map<Capability, Completer> _completers = {};

  Worker() {
    init();
  }

  void dispose() {
    _isolate.kill();
  }

  Future reuqest(dynamic message) async {
    await _isolateReady.future;
    final completer = new Completer();
    final requestId = new Capability();
    _completers[requestId] = completer;
    _sendPort.send(new _Request(requestId, message));
    return completer.future;
  }

  Future<void> init() async {
    final receivePort = ReceivePort();
    final errorPort = ReceivePort();
    errorPort.listen(print);
    receivePort.listen(_handleMessage);
    _isolate = await Isolate.spawn(
      _isolateEntry,
      receivePort.sendPort,
      onError: errorPort.sendPort,
    );
  }

  void _handleMessage(message) {
    if (message is SendPort) {
      _sendPort = message;
      _isolateReady.complete();
      return;
    }
    if (message is _Response) {
      final completer = _completers[message.requestId];
      if (completer == null) {
        print("Invalid request ID received.");
      } else if (message.success) {
        completer.complete(message.message);
      } else {
        completer.completeError(message.message);
      }
      return;
    }
    throw UnimplementedError("Undefined behavior for message: $message");
  }

  static void _isolateEntry(dynamic message) {
    SendPort sendPort;
    final receivePort = ReceivePort();

    receivePort.listen((dynamic message) async {
      if (message is _Request) {
        print('子线程收到:${message.message}');
        sendPort.send(_Response.ok(message.requestId, '处理后的消息'));
        return;
      }
    });

    if (message is SendPort) {
      sendPort = message;
      sendPort.send(receivePort.sendPort);
      return;
    }
  }
}

class _Request {
  /// The ID of the request so the response may be associated to the request's future completer.
  final Capability requestId;

  /// The actual message of the request.
  final dynamic message;

  const _Request(this.requestId, this.message);
}

class _Response {
  /// The ID of the request this response is meant to.
  final Capability requestId;

  /// Indicates if the request succeeded.
  final bool success;

  /// If [success] is true, holds the response message.
  /// Otherwise, holds the error that occured.
  final dynamic message;

  const _Response.ok(this.requestId, this.message) : success = true;

  const _Response.error(this.requestId, this.message) : success = false;
}
复制代码

感谢:

sg.jianshu.io/p/a4a871995…

juejin.cn/post/696136…

分类:
iOS
标签: