Flutter 真异步

前面说过 Flutter 实现异步的方式有:async、awiteFutrue,可是这些本质仍是 handle 队列那套,更况且消息队列仍是跑在 UI 线程里的,要是你真有什么耗时的操做放在这里妥妥的光剩卡了。因此该开新线程仍是得开,这里咱们来讲说怎么 new Isolate 和 Isolate 之间的通信,但后再来看看系统给咱们提供的简便函数:compute网络


Isolate 的建立和 Isolate 之间的通信

上文书 Isolate 之间是内存隔离的,单个 Isolate 内部是是以消息队列的方式执行的,这是 Isolate 的特征。语言之间概念基本趋同,只是实现和细微不一样,可是因概相同天然就会诞生相同的需求,Dart 天然提供了 Isolate 之间通信的方式:port 端口,能够很方便的实现 Isolate 之间的双向通信,原理是向对方的队列里写入任务异步

port 成对出现,分为:receivePort 接受端口SendPort 发送端口receivePort 能够本身生成对应的 SendPort,只要把 SendPort 传递给对方就能实现从 SendPort->receivePort 的通许,固然这是单项的,双向通信的其实也同样,对面把本身的 SendPort 传过来就成了async

var receivePort = ReceivePort();  
var sendPort = receivePort.sendPort;
复制代码

建立 Isolate 的 API 是:await Isolate.spawn(Function,SendPort),由于这是个异步操做,因此加上 awaitFunction 这是个方法,是新的线程执行的核心方法,和 run 方法同样的意思,SendPort 就是咱们要传给对面的通信器函数

var anotherIsolate = await Isolate.spawn(otherIsolateInit, receivePort.sendPort);
复制代码

Isolate.listen 监听方法咱们能够拿到这个 Isolate 传递过来的数据,Isolate 之间什么数据类型均可以传递,没必要作任何标记,确定是底层帮咱们实现好了,很省事性能

receivePort.listen((date) {
    print("Isolate 1 接受消息:data = $date");
});
复制代码

await receivePort.first 能够等待获取第一个返回结果,可是不能和 receivePort.listen 一块儿写,有冲突,只能选择一个ui

final sendPort = await receivePort.first as SendPort;
复制代码

Isolate 关闭很直接,在 main isolate 中对其控制的 Isolate 调用 kill 方法就好了加密

void stop(){
print("kill isolate");
isolate?.kill(priority: Isolate.immediate);
isolate =null;
}
复制代码

不废话了,上面很简单的,把原理一说你们都明白,下面直接看代码:spa


Isolate 单向通信

isolate 代码:线程

import 'dart:isolate';

var anotherIsolate;
var value = "Now Thread!";

void startOtherIsolate() async {
  var receivePort = ReceivePort();

  anotherIsolate = await Isolate.spawn(otherIsolateInit, receivePort.sendPort);

  receivePort.listen((date) {
    print("Isolate 1 接受消息:data = $date,value = $value");
  });
}

void otherIsolateInit(SendPort sendPort) async {
  value = "Other Thread!";
  sendPort.send("BB");
}
复制代码

运行代码:debug

import 'DartLib.dart';

void main(){
  startOtherIsolate();
}
复制代码

结果:

Isolate 1 接受消息:data = BB,value = Now Thread!
复制代码

Isolate 双向通信

isolate 代码:

import 'dart:isolate';

var anotherIsolate;
var value = "Now Thread!";

void startOtherIsolate() async {
  var receivePort = ReceivePort();
  var sendPort;

  anotherIsolate = await Isolate.spawn(otherIsolateInit, receivePort.sendPort);

  receivePort.listen((date) {
    if (date is SendPort) {
      sendPort = date as SendPort;
      print("双向通信创建成功");
      return;
    }
    print("Isolate 1 接受消息:data = $date");
    sendPort.send("AA");
  });
}

void otherIsolateInit(SendPort sendPort) async {
  value = "Other Thread!";

  var receivePort = ReceivePort();
  print("Isolate 2 接受到来自 Isolate 1的port,尝试创建同 Isolate 1的双向通信");

  receivePort.listen((date) {
    print("Isolate 2 接受消息:data = $date");
  });

  sendPort.send(receivePort.sendPort);

  for (var index = 0; index < 10; index++) {
    sendPort.send("BB$index");
  }
}
复制代码

运行代码:

import 'DartLib.dart';

void main(){
  startOtherIsolate();
}
复制代码

运行结果:

Isolate 2 接受到来自 Isolate 1的port,尝试创建同 Isolate 1的双向通信
双向通信创建成功
Isolate 1 接受消息:data = BB0
Isolate 1 接受消息:data = BB1
Isolate 1 接受消息:data = BB2
Isolate 1 接受消息:data = BB3
Isolate 1 接受消息:data = BB4
Isolate 1 接受消息:data = BB5
Isolate 1 接受消息:data = BB6
Isolate 1 接受消息:data = BB7
Isolate 1 接受消息:data = BB8
Isolate 1 接受消息:data = BB9
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
复制代码

系统 API:computer 函数

上面咱们本身 new 一个 Isoalte 并实现通信,多少有点麻烦,从封装的角度看其中代码基本是重复的,因此 Google 就提供了一个 API 来干这事:compute 方法

compute 方法是 Flutter 提供给咱们的(记住不是 Dart),compute 内部会建立一个 Isolate 并返回计算结果,体验上和一次性线程同样,性能多少有些浪费,可是也有使用范围

compute(function,value) compute 函数接受2个参数,第一个就是新线程的核心执行方法,第二个是传递过新线程的参数,能够是任何类型的数据,几个也能够,可是要注意,function 函数的参数设计要和 value 匹配

compute 方法在 import 'package:flutter/foundation.dart' 这个包里面

看个例子:

import 'dart:io';
import 'dart:isolate';
import 'package:flutter/foundation.dart';

void newTask() async {
  print("开始耗时计算,当前 isolate = ${Isolate.current.toString()}");
  var result = await compute(getName, "name");
  print(result);
}

String getName(String name) {
  print("正在获取结果中...,当前 isolate = ${Isolate.current.toString()}");
  sleep(Duration(seconds: 2));
  return "Name";
}
复制代码

运行代码:

newTask();
复制代码
I/flutter (24384): 开始耗时计算,当前 isolate = Instance of 'Isolate'
I/flutter (24384): 正在获取结果中...,当前 isolate = Instance of 'Isolate'
I/flutter (24384): Name
复制代码

compute 函数源码

compute 的源码不难,稍微用些新就能看懂,就是 new 了一个 isolate 出来,awite 第一个数据而后返回

//compute函数 必选参数两个,已经讲过了
Future<R> compute<Q, R>(ComputeCallback<Q, R> callback, Q message, { String debugLabel }) async {
  //若是是在profile模式下,debugLabel为空的话,就取callback.toString()
  profile(() { debugLabel ??= callback.toString(); });
  final Flow flow = Flow.begin();
  Timeline.startSync('$debugLabel: start', flow: flow);
  final ReceivePort resultPort = ReceivePort();
  Timeline.finishSync();
  //建立isolate,这个和前面讲的建立isolate的方法一致
  //还有一个,这里传过去的参数是用_IsolateConfiguration封装的类
  final Isolate isolate = await Isolate.spawn<_IsolateConfiguration<Q, R>>(
    _spawn,
    _IsolateConfiguration<Q, R>(
      callback,
      message,
      resultPort.sendPort,
      debugLabel,
      flow.id,
    ),
    errorsAreFatal: true,
    onExit: resultPort.sendPort,
  );
  final R result = await resultPort.first;
  Timeline.startSync('$debugLabel: end', flow: Flow.end(flow.id));
  resultPort.close();
  isolate.kill();
  Timeline.finishSync();
  return result;
}
复制代码

Isolate 使用场景

Isolate 虽好,但也有合适的使用场景,不建议滥用 Isolate,应尽量多的使用Dart中的事件循环机制去处理异步任务,这样才能更好的发挥Dart语言的优点

那么应该在何时使用Future,何时使用Isolate呢?一个最简单的判断方法是根据某些任务的平均时间来选择:

  • 方法执行在几毫秒或十几毫秒左右的,应使用Future
  • 若是一个任务须要几百毫秒或之上的,则建议建立单独的Isolate

除此以外,还有一些能够参考的场景

  • JSON 解码
  • 加密
  • 图像处理:好比剪裁
  • 网络请求:加载资源、图片