Flutter的isolate机制


Flutter是使用Dart语言为基础的,所以它的线程机制使用的也是Dart的线程机制。Dart 是单线程,Dart 为我们提供了 isolate,isolate 跟线程差不多,它可以理解为 Dart 中的线程。isolate 与线程的区别就是线程与线程之间是共享内存的,而 isolate 和 isolate 之间是不共享的,所以叫 isolate (隔离)。

简单说,可以把它理解为Dart中的线程。但它又不同于线程,更恰当的说应该是微线程,或者说是协程。它与线程最大的区别就是不能共享内存,因此也不存在锁竞争问题,两个Isolate完全是两条独立的执行线,且每个Isolate都有自己的事件循环,它们之间只能通过发送消息通信,所以它的资源开销低于线程。

大多数计算机中,甚至在移动平台上,都在使用多核 CPU。为了有效利用多核性能,开发者一般使用共享内存的方式让线程并发地运行。然而,多线程共享数据通常会导致很多潜在的问题,并导致代码运行出错。

为了解决多线程带来的并发问题,Dart 使用 isolates 替代线程,所有的 Dart 代码均运行在一个 isolates 中。每一个 isolates 有它自己的堆内存以确保其状态不被其它 isolates 访问。

每个 isolate 都拥有自己的事件循环及队列(MicroTask 和 Event)。这意味着在一个 isolate 中运行的代码与另外一个 isolate 不存在任何关联。

isolate定义

isolate是Dart对actor并发模式的实现。运行中的Dart程序由一个或多个actor组成,这些actor也就是Dart概念里面的isolate。isolate是有自己的内存和单线程控制的运行实体。isolate本身的意思是“隔离”,因为isolate之间的内存在逻辑上是隔离的。isolate中的代码是按顺序执行的,任何Dart程序的并发都是运行多个isolate的结果。因为Dart没有共享内存的并发,没有竞争的可能性所以不需要锁,也就不用担心死锁的问题。

由于isolate之间没有共享内存,所以他们之间的通信唯一方式只能是通过Port进行,而且Dart中的消息传递总是异步的。

我们可以看到isolate神似Thread,但实际上两者有本质的区别。操作系统内的线程之间是可以有共享内存的而isolate没有,这是最为关键的区别。

实现

我们可以阅读Dart源码里面的isolate.cc文件看看isolate的具体实现。我们可以看到在isolate创建的时候有以下几个主要步骤:

  1. 初始化isolate数据结构
  2. 初始化堆内存(Heap)
  3. 进入新创建的isolate,使用跟isolate一对一的线程运行isolate
  4. 配置Port
  5. 配置消息处理机制(Message Handler)
  6. 配置Debugger,如果有必要的话
  7. 将isolate注册到全局监控器(Monitor)

我们可以看到Dart本身抽象了isolate和thread,实际上底层还是使用操作系统的提供的OSThread。


flutter toast overlay封装 flutter isolate_isolate

isolate实战


接下来我们来看如何创建并使用isolate来执行异步任务。

创建isolate

使用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});

spawn方法有两个必须的参数:

  • 函数entryPoint:第一个是需要运行在新Isolate的耗时函数,函数必须是顶级函数或静态方法
  • 参数message:第二个是动态消息,该参数通常用于传送主Isolate的SendPort对象

isolate之间的通信

由于isolate之间没有共享内存,所以他们之间的通信唯一方式只能是通过Port进行,而且Dart中的消息传递总是异步的。

两个Isolate是通过两对Port对象通信,一对Port分别由用于接收消息的ReceivePort对象,和用于发送消息的SendPort对象构成。其中SendPort对象不用单独创建,它已经包含在ReceivePort对象之中。需要注意,一对Port对象只能单向发消息,这就如同一根自来水管,ReceivePort和SendPort分别位于水管的两头,水流只能从SendPort这头流向ReceivePort这头。因此,两个Isolate之间的消息通信肯定是需要两根这样的水管的,这就需要两对Port对象。

ReceivePort对象通过调用listen方法,传入一个函数可用来监听并处理发送来的消息。SendPort对象则调用send()方法来发送消息。send方法传入的参数可以是null,num, bool, double,String, List ,Map或者是自定义的类。

import 'dart:isolate';
import  'dart:io';

void main() {
  print("main isolate start");
  create_isolate();
  print("main isolate end");
}

// 创建一个新的 isolate
create_isolate() async{
  ReceivePort rp = new ReceivePort();
  SendPort port1 = rp.sendPort;

  Isolate newIsolate = await Isolate.spawn(doWork, port1);

  SendPort port2;
  rp.listen((message){
    print("main isolate message: $message");
    if (message[0] == 0){
      port2 = message[1];
    }else{
      port2?.send([1,"这条信息是 main isolate 发送的"]);
    }
  });
}

// 处理耗时任务
void doWork(SendPort port1){
  print("new isolate start");
  ReceivePort rp2 = new ReceivePort();
  SendPort port2 = rp2.sendPort;

  rp2.listen((message){
    print("doWork message: $message");
  });

  // 将新isolate中创建的SendPort发送到主isolate中用于通信
  port1.send([0, port2]);
  // 模拟耗时5秒
  sleep(Duration(seconds:5));
  port1.send([1, "doWork 任务完成"]);

  print("new isolate end");
}

两个进程都双向绑定了消息通信的通道,即使新的Isolate中的任务完成了,它的进程也不会立刻退出,因此,当使用完自己创建的Isolate后,最好调用newIsolate.kill(priority: Isolate.immediate);将Isolate立即杀死。

Flutter 中创建Isolate

在Dart中创建一个Isolate都显得有些繁琐,可惜的是Dart官方并未提供更高级封装。但是,如果想在Flutter中创建Isolate,则有更简便的API,这是由Flutter官方进一步封装ReceivePort而提供的更简洁API。

使用compute函数来创建新的Isolate并执行耗时任务:

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

// 创建一个新的Isolate,在其中运行任务doWork
create_new_task() async{
  var str = "New Task";
  var result = await compute(doWork, str);
  print(result);
}


String doWork(String value){
  print("new isolate doWork start");
  // 模拟耗时5秒
  sleep(Duration(seconds:5));

  print("new isolate doWork end");
  return "complete:$value";
}

compute函数有两个必须的参数:

  1. 第一个是待执行的函数,这个函数必须是一个顶级函数,不能是类的实例方法,可以是类的静态方法。
  2. 第二个参数为动态的消息类型,可以是被运行函数的参数。需要注意,使用compute应导入’package:flutter/foundation.dart’包。

代码demo

完整的Isolate创建和使用示例:

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

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

//一个普普通通的Flutter应用的入口
//main函数这里有async关键字,是因为创建的isolate是异步的
void main() async{
  runApp(MyApp());
  
  //asyncFibonacci函数里会创建一个isolate,并返回运行结果
  print(await asyncFibonacci(20));
}

//这里以计算斐波那契数列为例,返回的值是Future,因为是异步的
Future<dynamic> asyncFibonacci(int n) async{
  //首先创建一个ReceivePort,为什么要创建这个?
  //因为创建isolate所需的参数,必须要有SendPort,SendPort需要ReceivePort来创建
  final response = new ReceivePort();
  //开始创建isolate,Isolate.spawn函数是isolate.dart里的代码,_isolate是我们自己实现的函数
  //_isolate是创建isolate必须要的参数。
  await Isolate.spawn(_isolate,response.sendPort);
  //获取sendPort来发送数据
  final sendPort = await response.first as SendPort;
  //接收消息的ReceivePort
  final answer = new ReceivePort();
  //发送数据
  sendPort.send([n,answer.sendPort]);
  //获得数据并返回
  return answer.first;
}

//创建isolate必须要的参数
void _isolate(SendPort initialReplyTo){
  final port = new ReceivePort();
  //绑定
  initialReplyTo.send(port.sendPort);
  //监听
  port.listen((message){
    //获取数据并解析
    final data = message[0] as int;
    final send = message[1] as SendPort;
    //返回结果
    send.send(syncFibonacci(data));
  });
}

int syncFibonacci(int n){
  return n < 2 ? n : syncFibonacci(n-2) + syncFibonacci(n-1);
}

ps:如果觉得本文对你有所帮助,动动你的小手,在文章下方 “点赞” 和 “收藏” 支持下吧~😁