【Flutter入门到进阶】Dart进阶篇---Dart多线程异步原理

news2024/11/14 21:03:59

1 Isolate 

1.1 什么是Isolate

1.1.1 概念

        线程?异步?隔离?到底什么意思?

        Isolate中文意思是隔离,从使用角度来说是Dart的线程,但是从本质虚拟机的实现角度来讲Isolate是一组封装。

        isolate可以理解为dart中的线程,但它又不同于线程,准确的说应该叫做协程,协程最大的优势就是它具有极高的执行效率,因为携程中子程序的调用不需要线程的切换,所以对于线程数量越大的程序来说协程的优势就越明显。每个isolate都有自己独立的执行线程和事件循环,以及内存,所以isolate之间不存在锁竞争的问题,各个isolate之间通过消息通信。

1.1.2 图例

1.1.3 设计目的

        首先说目前由移动端页面(包含Android、iOS、Web)构建的特性—树形结构构建布局、布局解析抽象、绘制、渲染,这一系列的复杂步骤导致必须在同一个线程完成。因为多线程操作页面UI元素会有并发的问题,有并发就必须要加锁,加锁就会降低执行效率。所以强制在同一线程中操作UI是最好的选择。况且在Flutter中,开发者面对的只有一个主Isolate,在Isolate中可以通过事件队列来实现异步(网络请求、文件IO)等。

1.1.4 说明

        1.为了达到设计目的,采取隔离设计去掉锁的应用

        2.锁去掉后,线程间的信息通信需要建立通信机制来完成,参考linux os 底层策略上来讲本质上就是走IO那套体系来完成

1.2 Isolate本质

1.1.1 图例

1.1.2 说明

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

1.3 Isolate组成

1.3.1 Stack用于存放函数调用上下文和调用链路信息。

1.3.2 Heap用于存放对象,堆内存回收管理和java类似。

1.3.3 Queue用于存放异步回调,分为微事件队列(MicroTaskQueue)和微任务队列(EventQueue)。

1.3.4 EventLoop用于处理异步回调。

1.4 Isolate底层逻辑

1.4.1 说明

        虽然在内存表现上,Isolate内存隔离性像是进程的特点。但是从实现上不可能把Isolate作为一个进程,因为进程太重了,每新建一个进程,内核系统都会为新进程创建独立的虚拟内存,保存进程相关的数据结构,并且进程切换效率比较低。所以从可行性上来说Isolate的本质应该是一个线程。也就是说Isolate是通过线程实现的。我们使用多个Isolate也就是使用多个线程,只不过与传统线程不同的是,Isolate之间内存不共享,但可以通过通信机制互通。

1.4.2 Isolate如何实现内存隔离

// vm/isolate.cc Isolate* 
Isolate::InitIsolate(
    const char* name_prefix,                               
    IsolateGroup* isolate_group,                               
    const Dart_IsolateFlags& api_flags,                               
    bool is_vm_isolate) {   
        Isolate* result = new Isolate(isolate_group, api_flags);   
        ...        
        Heap::Init(result,              
        is_vm_isolate? 0  
        // New gen size 0; VM isolate should only allocate in old.                  
        : FLAG_new_gen_semi_max_size * MBInWords,              
        (is_service_or_kernel_isolate ? kDefaultMaxOldGenHeapSize 
        : FLAG_old_gen_heap_size) 
        *MBInWords);                       
        ...        
    }

        从代码中可以看Isolate的堆内存也被区分为新生代和老年代两块区域,Dart虚拟机针对不同的区域执行不同的垃圾回收策略:

        新生代采用复制清除算法,针对频繁创建销毁的页面控件对象,可以从内存层面实现快速分配和回收。

        老年代采用标记清除和标记整理两种算法,来适应不同的内存回收场景,尽量保证UI的流畅性。

        这里也就解释了Dart中内存分配模型,和高效垃圾的回收机制。

1.5 运行图例

1.6 Isolate模式

在dart中编写的代码分为两种类型

1.6.1 同步模式

即正常编写的代码。  

        BIO模式

1.6.2 异步模式

一些返回类型为Future和Stream的函数。   

        NIO模式

1.7 Isolate使用案例

1.7.1 常规使用

import 'dart:isolate'; 
void main(){   
    print("main begin");   
    Isolate.spawn((message) {     
        print("匿名函数线程:$message");   
    }, "inner msg...");   
    Isolate.spawn(newThread1, "hello 1");   
    Isolate.spawn(newThread2, "hello 2");   
    Isolate.spawn(newThread3, "hello 3");   
    print("main end"); 
} 
void newThread1(String msg){   
    print("Thread 1 msg:$msg"); 
} 
void newThread2(String msg){   
    print("Thread 2 msg:$msg"); 
} 
void newThread3(String msg){   
    print("Thread 3 msg:$msg"); 
}

1.7.2 isolate.spwanUri

//当前文件

import 'dart:isolate'; 
///isolate spawnUri方式 
void main() {   
    print("main begin");   
    test1();   
    newThread();   
    test2();   
    print("main end"); 
} 
void test1(){   
    print("test1....."); 
} 
void test2(){   
    print("test2....."); 
} 
///参数的定义可以随意,参数中接收的SendPort是需要通信的发送端口 
@sendPort 
void newThread(){   
    print("新线程.....");   
    ReceivePort receivePort = ReceivePort();   
    SendPort sendPort = receivePort.sendPort;   
    Isolate.spawnUri(Uri(path: "./isolate_spawnUri_task.dart"),["msg1","msg2","msg3"], sendPort);   
    //监听   
    receivePort.listen((message) {     
        print("主线程接收到来自子线程消息:${message}");     
        switch(message[1]){       
            case 0:         
                //子线程正在处理初始化数据...         
                print("接收到初始化消息");         
            break;       
            case 1:         
                //子线程异步数据正在处理中..         
                print("接收到处理中状态消息");         
            break;       
            case 2:         
                //子线程数据处理完整         
                print("接收到任务完成消息");         
                receivePort.close();        
            break;     
        }   
    }); 
}


//目标文件

import 'dart:isolate'; 
import 'dart:io'; 
///isolate spawnUri方式 
void main(List<String> args,SendPort sendPort) {   
    print("isolate_spawnUri_task.dart begin");   
    print("接收到相关参数:$args");   
    sendPort.send(["开始执行异步操作",0]);   
    sleep(Duration(seconds: 2));   
    sendPort.send(["加载数据中...",1]);   
    sleep(Duration(seconds: 2));   
    sendPort.send(["异步任务完成",2]);   
    sleep(Duration(seconds: 2));   
    print("isolate_spawnUri_task.dart end"); 
}

1.8 Isolate通信

1.8.1 说明

        创建Isolate时需要指定一个接收端口(ReceivePort)的发送端口(SendPort),调用者可以通过这个发送端口发送数据到其他的Isolate中ReceivePort的listen中,这种机制被称为消息传递(message passing)

        既然是内存隔离的,那么在调用者所在Isolate发送的消息数据是怎么传递到接收者所在的Isolate中的呢?也就是说Isolate通信的底层逻辑是什么呢?

        答案是map_变量,map_是一个Hash列表。是在Dart虚拟启动时初始化的,所以map_变量是存在于Dart虚拟机所属内存的,而这块内是各个Isolate共享的

1.8.2 通信

import 'dart:isolate';

///isolate 通信 - 单向
void main() async{
  print("main begin");

  ReceivePort receivePort = ReceivePort();
  Isolate.spawn(newThread,["你好",receivePort.sendPort]);
  //方案1:通过listen进行监听
  // receivePort.listen((message) {
  //   print("listen方式-主线程接收到消息:$message");
  // });
  //方案2:通过 await关键字与async关键字建立阻塞通道
  var msg = await receivePort.first;
  //上面这两种方案上只选选择一种处理
  //原因:这里dart对于数据的等待接收,我们可以看做为socket的BIO与NIO
  //listen方案实际上就是利用select,epoll这种方案在进行循环监听
  //await 就是read的阻塞式等待


  print("await方式-主线程接收到消息:$msg");
  print("main end");
}


///参数的定义可以随意,参数中接收的SendPort是需要通信的发送端口
///@sendPort
void newThread(var message){
  String msg = message[0];
  SendPort sendPort = message[1];
  print("通过参数传递的数据1:$msg");

  //通过传递过来的sendPort给主线程回消息
  sendPort.send("这个是子线程给主线程回的消息!!!");
}

1.8.3 为什么将Isolate设计成隔离的

        1、首先说目前由移动端页面(包含Android、iOS、Web)构建的特性—树形结构构建布局、布局解析抽象、绘制、渲染,这一系列的复杂步骤导致必须在同一个线程完成。**因为多线程操作页面UI元素会有并发的问题,有并发就必须要加锁,加锁就会降低执行效率。所以强制在同一线程中操作UI是最好的选择。**况且在Flutter中,开发者面对的只有一个主Isolate,在Isolate中可以通过事件队列来实现异步(网络请求、文件IO)等。所以不需要再使用其他线程完成异步。

        2、每当有页面交互时,必定会引起布局变化而重新绘制,这个过程会有频繁的大量的UI控件的创建和销毁,这就涉及到了耗时内存分配和回收。而这些较短生命周期的对象是存放在堆内存的新生代的,当虚拟机回收新生代内存时是要stop the world的,在Android或iOS中,各个线程共用一块堆内存,当非UI线程频繁申请、释放内存时也会触发垃圾回收,所以会间接影响UI线程的运行。

        Dart为了解决这个问题,就每个Isolate(看做线程)分配各自的一块堆内存,并且独自管理内。这样的策略使得内存的分配和回收变得简单高效,并且不受其他Isolate的影响。

1.9 总结

        1、Dart中向应用层提供了线程的封装——Isolate。应用层是不能创建线程的,只能使用Isolate

        2、Isolate与传统的线程不同的是,内存隔离

        3、Isolate设计成隔离的,是出于移动端页面UI构建特性考虑。第一点,UI绘制必须在同一线程内完成,所以强制同一线程是最好的选择。第二点,传统的线程内存共享,其他线程频繁的申请释放内存会触发垃圾回收,间接影响UI线程运行

2 Dart io 

2.1 说明

         I/O库使命令行应用程序能够读写文件和浏览目录。读取文件内容有两种选择:一次读取全部内容,或流式读取。一次读取一个文件需要足够的内存来存储该文件的所有内容。如果文件非常大,或者您希望在读取文件时对其进行处理,则应使用流,如Streaming file contents中所述

2.2 将文件作为文本读取:

         读取使用UTF-8编码的文本文件时,可以使用readAsString()读取整个文件内容。当单独的行很重要时,可以使用readAsLines() 。在这两种情况下,都会返回一个Future对象,该对象以一个或多个字符串的形式提供文件的内容。
代码
Future<void> main() async {
  var config = File('config.txt');
  // Put the whole file in a single string.
  var stringContents = await config.readAsString();
  print('The file is ${stringContents.length} characters long.');
  // Put each line of the file into its own string.
  var lines = await config.readAsLines();
  print('The file is ${lines.length} lines long.');
}

2.3 将文件作为二进制读取:

        下面的代码将整个文件作为字节读取到整数列表中。对readAsBytes()的调用返回一个Future,该函数在可用时提供结果
代码
Future<void> main() async {
  var config = File('config.txt');
  var contents = await config.readAsBytes();
  print('The file is ${contents.length} bytes long.');
}

2.4 错误处理

        要捕获错误,使其不会导致未捕获的异常,你可以在Future注册catchError处理程序,或者(在异步函数中)使用try-catch:
代码
Future<void> main() async {   
    var config = File('config.txt');   
    try {     
        var contents = await config.readAsString();     
        print(contents);   
    } catch (e) {     
        print(e);   
    } 
}

2.5 用Stream读取文件:

        使用Stream读取文件,一次读取一点。你可以使用Stream API或await for。
代码
import 'dart:io';
import 'dart:convert';

Future<void> main() async {
  var config = File('config.txt');
  Stream<List<int>> inputStream = config.openRead();
  var lines = utf8.decoder.bind(inputStream).transform(const LineSplitter());
  try {
    await for (final line in lines) {
      print('Got ${line.length} characters from stream');
    }
    print('file is now closed');
  } catch (e) {
    print(e);
  }
}

2.6 写文件

        您可以使用IOSink将数据写入文件。使用File openWrite()方法获取可以写入的IOSink。默认模式(mode)为FileMode.write,完全覆盖文件中的现有数据。
代码
var logFile = File('log.txt'); 
var sink = logFile.openWrite(); 
sink.write('FILE ACCESSED ${DateTime.now()}\n'); 
await sink.flush(); 
await sink.close();
将数据添加到文件末尾,可以把可选参数mode指定为FileMode.append:
var sink = logFile.openWrite(mode: FileMode.append)
添加二进制数据,使用add(List<int> data)

2.7 列出目录中的文件

        查找目录的所有文件和子目录是一项异步操作。方法返回遇到文件或目录时对象的Stream。
代码
Future<void> main() async {
  var dir = Directory('tmp');
  try {
    var dirList = dir.list();
    await for (final FileSystemEntity f in dirList) {
      if (f is File) {
        print('Found file ${f.path}');
      } else if (f is Directory) {
        print('Found dir ${f.path}');
      }
    }
  } catch (e) {
    print(e.toString());
  }
}

2.8 其他常用方法

创建一个文件或目录:create()(针对文件或目录)
删除一个文件或目录:delete()(针对文件或目录)
获取文件的长度:length()(针对文件)
获取对文件的随机访问:open()(针对文件)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/356788.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

群晖-第1章-IPV6的DDNS

群晖-第1章-IPV6的DDNS 方案&#xff1a;腾讯云群晖DS920 本文参考群晖ipv6 DDNS-go教程-牧野狂歌&#xff0c;感谢原作者的分享。 这篇文章只记录了我需要的部分&#xff0c;其他的可以查看原文&#xff0c;原文还记录了更多的内容&#xff0c;可能帮到你。 一、购买域名 …

【基于众包标注的语文教材句子难易度评估研究 论文精读】

基于众包标注的语文教材句子难易度评估研究 论文精读信息摘 要0 引言1 相关研究2 众包标注方法3 语料库构建3.1 数据收集3.1 基于五点量表的专家标注3.3 基于成对比较的众包标注4 特征及模型4.1 特征抽取4.2 模型与实验设计4.2.1 任务一:单句绝对难度评估4.2.2 任务二:句对相对…

《JavaScript百炼成仙》,简单但是挺有效的

编程之修&#xff0c;重在积累&#xff0c;而非资质。资质虽然重要&#xff0c;可是后天的努力更不可少。 《JavaScript百炼成仙》是一本以玄幻小说的形式&#xff0c;来讲述JavaScript的知识。 此篇仅仅是我快速阅读《JavaScript百炼成仙》这本书的笔记&#xff0c;流水账笔…

MySQL进阶篇之InnoDB存储引擎

06、InnoDB引擎 6.1、逻辑存储结构 表空间&#xff08;Tablespace&#xff09; 表空间在MySQL中最终会生成ibd文件&#xff0c;一个mysql实例可以对应多个表空间&#xff0c;用于存储记录、索引等数据。 段&#xff08;Segment&#xff09; 段&#xff0c;分为数据段&#x…

python基于vue微信小程序 房屋租赁出租系统

目录 1 绪论 1 1.1课题背景 1 1.2课题研究现状 1 1.3初步设计方法与实施方案 2 1.4本文研究内容 2 2 系统开发环境 4 2.1 2.2MyEclipse环境配置 4 2.3 B/S结构简介 4 2.4MySQL数据库 5 2. 3 系统分析 6 3.1系统可行性分析 6 3.1.1经济可行性 6 3.1.2技术可行性 6 3.1.3运行可行…

面试准备知识点与总结——(虚拟机篇)

目录JVM的内存结构JVM哪些部分会发生内存溢出方法区、永久代、元空间三者之间的关系JVM内存参数JVM垃圾回收算法1.标记清除法2.标记整理3.标记复制说说GC和分代回收算法三色标记与并发漏标的问题垃圾回收器项目中什么时候会内存溢出&#xff0c;怎么解决类加载过程三个阶段何为…

Docker 架构简介

Docker 架构 Docker 包括三个基本概念: 镜像&#xff08;Image&#xff09;&#xff1a;Docker 镜像&#xff08;Image&#xff09;&#xff0c;就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。容器&am…

【力扣-LeetCode】64. 最小路径和 C++题解

64. 最小路径和难度中等1430收藏分享切换为英文接收动态反馈给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。说明&#xff1a;每次只能向下或者向右移动一步。示例 1&#xff1a;输入&#xff…

实例1:控制树莓派板载LED灯闪烁

实例1&#xff1a;控制树莓派板载LED灯闪烁 实验目的 通过背景知识学习&#xff0c;了解四足机器人mini pupper搭载的微型控制计算机&#xff1a;树莓派。通过对树莓派板载LED灯的状态读写控制&#xff0c;熟悉树莓派本身的操作及Linux中文件的读写。掌握常见函数time.sleep(…

初识Hadoop,走进大数据世界

文章目录数据&#xff01;数据&#xff01;遇到的问题Hadoop的出现相较于其他系统的优势关系型数据库网格计算本文章属于Hadoop系列文章&#xff0c;分享Hadoop相关知识。后续文章中会继续分享Hadoop的组件、MapReduce、HDFS、Hbase、Flume、Pig、Spark、Hadoop集群管理系统以及…

rewrite中的if、break、last

目录 rewrite 作用&#xff1a; 依赖&#xff1a; 打开重定向日志&#xff1a; if 判断&#xff1a; location {} 本身有反复匹配执行特征 在 location 中加入 break 和 last &#xff08;不一样&#xff09; 加了break后&#xff0c;立刻停止向下 且 跳出。 加了last&#xf…

Windows 版本ffmpeg编译概述

在使用ffmpeg过程当中&#xff0c;ffmpeg在Linux(包括mac,android)编译非常容易,直接configure,make即可&#xff0c;Android需要交叉编译,在windows就比较麻烦&#xff0c;庆幸的是ffmpeg官方提供已编译好Windows版本的二进制库&#xff08;http://ffmpeg.org/download.html#b…

使用vector<char>作为输入缓冲区

一、引言 当我们编写代码&#xff1a;实现网络接收、读取文件内容等功能时&#xff0c;我们往往要在内存中开辟一个输入缓冲区(又名&#xff1a;input buffer/读缓冲区&#xff09;来存贮接收到的数据。在C里面我们可以用如下方法开辟输入缓冲区。 ①使用C语言中的数组&#x…

【Spring教程】1.Spring概述

1、概述 1.1、Spring是什么&#xff1f; Spring 是一款主流的 Java EE 轻量级开源框架 &#xff0c;Spring 由“Spring 之父”Rod Johnson 提出并创立&#xff0c;其目的是用于简化 Java 企业级应用的开发难度和开发周期。Spring的用途不仅限于服务器端的开发。从简单性、可测…

Android Compose Bloom 项目实战 (四) : 主页

1. 前言 上篇文章 我们实现了 Compose Bloom项目的开发页&#xff0c;这篇文章接着上文&#xff0c;来介绍主页的开发。 2. 分析页面布局 根据UI稿我们可知&#xff0c;这个页面有一个底部切换的BottomBar&#xff0c;还有一个搜索框&#xff0c;一个横向的列表以及一个竖向的…

RNN GRU模型 LSTM模型图解笔记

RNN模型图解引用RNN模型GRULSTM深度RNN双向循环神经网络引用 动手学深度学习v2–李沐 LSTM长短期记忆网络3D模型–B站up梗直哥丶 RNN模型 加入了一个隐变量&#xff08;状态)&#xff0c;隐变量由上个隐变量和上一个输入而更新&#xff0c;这样模型就可以达到具有短期记忆的效…

加油站会员管理小程序实战开发教程10

上一篇我们介绍了计算距离及到店导航的功能,本篇我们介绍一下今日油价的功能。 如果要按日显示最新的数据,那么我们首先需要有数据源来存放每日的油价数据。这里涉及数据源的时候要考虑你的数据是只录入一条,还是每日录入一条。 录入一条呢,比较简单,但有个问题是如果我…

制作for arm64 cpu架构的docker镜像

我前段时间买了个阿里云&#xff0c;没有留意CPU的架构是ARM的&#xff0c;结果部署系统的时候就发现出问题了&#xff0c;部署在docker里的容器实例根本跑不起来&#xff0c;提示什么执行文件格式错误&#xff08;“exec format error”&#xff09;。 究其原因&#xff0c;…

spring注解方式整合Dubbo源码解析

系列文章目录 前言 本节我们的Dubbo源码版本基于2.6.x 在前一章我们的整合案例中&#xff0c;我们有几个比较关键的步骤&#xff1a; 在启动类上标注了EnableDubbo注解在provider类上面标注了Service注解来提供dubbo服务在消费的时候通过Reference注解引入dubbo服务在配置文件…

软考中级-面向对象

面向对象基础&#xff08;1&#xff09;类类分为三种&#xff1a;实体类&#xff08;世间万物&#xff09;、接口类&#xff08;又称边界类&#xff0c;提供用户与系统交互的方式&#xff09;、控制类&#xff08;前两类之间的媒介&#xff09;。对象&#xff1a;由对象名数据&…