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。

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;//接收消息的ReceivePortfinal 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:如果觉得本文对你有所帮助,动动你的小手,在文章下方 “点赞” 和 “收藏” 支持下吧~

Flutter的isolate异步线程机制及使用实战详解相关推荐

  1. flutter 获取android 还是ios_Flutter完整开发实战详解(二十、 Android PlatformView 和键盘问题)...

    作为系列文章的第二十篇,本篇将结合官方的技术文档科普 Android 上 PlatformView 的实现逻辑,并且解释为什么在 Android 上 PlatformView 的键盘总是有问题. 为什 ...

  2. 一文搞懂线程池原理——Executor框架详解

    文章目录 1 使用线程池的好处 2 Executor 框架 2.1 Executor 框架结构 2.2 Executor 框架使用示意图 2.3 Executor 框架成员 2.3.1 Executo ...

  3. future java 原理_Java线程池FutureTask实现原理详解

    前言 线程池可以并发执行多个任务,有些时候,我们可能想要跟踪任务的执行结果,甚至在一定时间内,如果任务没有执行完成,我们可能还想要取消任务的执行,为了支持这一特性,ThreadPoolExecutor ...

  4. Java 解析线程的几种状态详解

    Java 解析线程的几种状态详解 1. 线程的5种状态 从操作系统层面上,任何线程一般都具有五种状态,即创建.就绪.运行.阻塞.终止. (1) 新建状态(NEW) 在程序中用构造方法创建一个新线程时, ...

  5. 【JVM】Java垃圾回收机制(GC)详解

    Java垃圾回收机制(GC)详解 一.为什么需要垃圾回收? 如果不进行垃圾回收,内存迟早都会被消耗空,因为我们在不断的分配内存空间而不进行回收.除非内存无限大,我们可以任性的分配不回收,但是事实并非如 ...

  6. Mapreduce源码分析(一):FileInputFormat切片机制,源码详解

    FileInputFormat切片机制,源码详解 1.InputFormat:抽象类 只有两个抽象方法 public abstract List<InputSplit> getSplits ...

  7. java condition详解_Java使用Condition控制线程通信的方法实例详解

    Java使用Condition控制线程通信的方法实例详解 发布于 2020-4-20| 复制链接 摘记: 本文实例讲述了Java使用Condition控制线程通信的方法.分享给大家供大家参考,具体如下 ...

  8. java 重启线程_java 可重启线程及线程池类的设计(详解)

    了解JAVA多线程编程的人都知道,要产生一个线程有两种方法,一是类直接继承Thread类并实现其run()方法:二是类实现Runnable接口并实现其run()方法,然后新建一个以该类为构造方法参数的 ...

  9. wxpython多线程 假死_wxpython多线程防假死与线程间传递消息实例详解

    wxpython中启用线程的方法,将GUI和功能的执行分开. 网上关于python多线程防假死与线程传递消息是几年前的,这里由于wxpython和threading模块已经更新最新,因此给出最新修改代 ...

  10. Flutter完整开发实战详解(二、 快速开发实战篇) | 掘金技术征文

     作为系列文章的第二篇,继<Flutter完整开发实战详解(一.Dart语言和Flutter基础)>之后,本篇将为你着重展示:如何搭建一个通用的Flutter App 常用功能脚手架,快速 ...

最新文章

  1. 为Win7 Win8右键菜单发送到添加常用位置(SendTo)
  2. phpwind自定义推送模块
  3. Java对象排序、中文排序、SortedSet排序使用和源码讲解
  4. Qt开发MQTT(二) 之第三方QMQTT
  5. 如何高效学习前端新知识,我推荐这些~
  6. 文本分类模型_多标签文本分类、情感倾向分析、文本实体抽取模型如何定制?...
  7. Linux协议栈:基于ping流程窥探Linux网络子系统,及常用优化方法
  8. 2015-01-11 在SQL2008创建一个数据库
  9. as3 操作图片,获取,设置实际像素,扣除透明区域
  10. Win-MASM64汇编语言-MOV/MOVSB/MOVSW/MOVSD/REP/REPZ/REPE/REPNZ/REPNE
  11. 800变频器故障代码_变频器通用故障码意义汇总讲解
  12. Mac实时远程抓Ubuntu的网络包
  13. Java进阶篇设计模式之三 ----- 建造者模式和原型模式
  14. Tomcat7下配置SSI,同时解决中文乱码问题
  15. 最短路径spfa算法
  16. Kaldi-Timit 训练
  17. 第2章 基础设施即服务(IaaS)-2-Docker
  18. 5.5 时间序列预测
  19. Dubbo分布式事务处理--视频教程
  20. 30天自制操作系统Day6

热门文章

  1. Android系统驱动介绍
  2. 快手短视频怎么同步到头条?
  3. Qt之简易版网络调试助手
  4. python spilt()函数
  5. linux 目录权限上下文,谢烟客---------Linux之文件安全上下文及特殊权限位
  6. python:实现Lempel-Ziv算法(附完整源码)
  7. mysql错误1548 Cannot load from mysql.proc的最终解决方法
  8. 基于AM5728核心板的户外工作站可靠性和便捷性设计
  9. 什么邮箱最好用?主流邮箱品牌如何选择
  10. 在Ubuntu中安装并配置Pycharm教程