flutter的代码运行流程

本文解析了Dart的单线程模型,包括微任务队列和事件队列的工作原理,以及它们如何影响程序执行流程。通过实例展示了事件任务和微任务的执行顺序,并讨论了事件运行卡顿的问题,以及Isolate线程如何实现多线程间的内存隔离。

Dart 单线程
单线程在流畅性方面有一定安全保障,这点在 JavaScript 中存在类似的机制原理,其核心是分为主线程、微任务和宏任务。主线程执行主业务逻辑,网络 I/O 、本地文件 I/O 、异步事件等相关任务事件,应用事件驱动方式来执行。在 Dart 中同样是单线程执行,其次也包含了两个事件队列,一个是微任务事件队列,一个是事件队列。

微任务队列

微任务队列包含有 Dart 内部的微任务,主要是通过 scheduleMicrotask 来调度。

事件队列

事件队列包含外部事件,例如 I/O 、 Timer ,绘制事件等等。

事件循环
既然 Dart 包含了微任务和事件任务:

首先是执行 main 函数,并生产两个相应的微任务和事件任务队列;

判断是否存在微任务,有则执行,执行完成后继续判断是否还存在微任务,无则判断是否存在事件任务;如果没有可执行的微任务,则判断是否存在事件任务,有则执行,无则继续返回判断是否还存在微任务;在微任务和事件任务执行过程中,同样会产生微任务和事件任务,因此需要再次判断是否需要插入微任务队列和事件任务队列。

在这里插入图片描述
为了验证上面的运行原理,我实现了下面的示例代码,首先 import async 库,然后在 main 函数中首先打印 flow start ,接下来执行一个微任务事件,再执行一个事件任务,最后再打印 flow end 。

import 'dart:async';
void main() {
	print('flow start'); // 执行打印开始 
	// 执行判断为事件任务,添加到事件任务队列
	Timer.run((){ 
       print('event'); // 执行事件任务,打印标记
   	});
   	// 执行判断为微任务,添加到微任务队列 
	scheduleMicrotask((){ 
        print('microtask'); // 执行微任务,打印标记
    });
	print('flow end'); // 打印结束标记
}

代码的实际运行过程如下:

首先主线程逻辑,执行打印 start ;

执行 Timer,为事件任务,将其增加到事件任务队列中;

执行 scheduleMicrotask,为微任务队列,将其增加到微任务队列中;

执行打印 flow end;

判断是否存在微任务队列,存在则执行微任务队列,打印 mcrotask;

判断是否还存在微任务队列,无则判断是否存在事件任务队列,存在执行事件任务队列,打印 event。

flow start
flow end
microtask
event

在这里插入图片描述
疑问1,为什么事件任务都执行完成了,还需要继续再循环判断是否有微任务?
核心解释是:微任务在执行过程中,也会产生新的事件任务,事件任务在执行过程中也会产生新的微任务。产生的新微任务,按照执行流程,需要根据队列方式插入到任务队列最后。

我们通过代码来看下该过程。下面一段代码, import async 库,第一步打印 start , 然后执行一个事件任务,在事件任务中打印 event 。接下来增加了一个微任务事件,在微任务事件中打印 microtask in event 。第二步执行微任务事件,在微任务事件中打印 microtask ,并且在其中增加事件任务队列,事件任务队列中打印 event in microtask ,最后再打印 flow end

import 'dart:async';
void main() {
	print('flow start'); // 执行打印开始
    // 执行判断为事件任务,添加到事件任务队列
	Timer.run((){ 
       	print('event'); // 执行事件任务,打印事件任务标记
        // 执行判断为微任务,添加到微任务队列 
       	scheduleMicrotask((){ 
        	print('microtask in event'); // 执行微任务,打印微任务标记
    	});
   	});
  // 执行判断为微任务,添加到微任务队列 
	scheduleMicrotask((){ 
        print('microtask'); // 执行微任务,打印微任务执行标记
        // 执行判断为事件任务,添加到事件任务队列 
        Timer.run((){ 
        	print('event in microtask'); // 执行事件任务,打印事件任务标记
        });
    });
	print('flow end'); // 打印结束标记
}

代码的实际运行过程如下:

首先还是依次执行打印 flow start ;

执行 Timer 为事件任务,添加事件任务队列中;

执行 scheduleMicrotask 为微任务,添加到微任务队列中;

打印 end ;

执行微任务队列,打印 microtask ,其中包括了事件任务,将事件任务插入到事件任务中;

执行事件任务队列,打印 event ,其中包括了微任务,将微任务插入到微任务队列中;

微任务队列存在微任务,执行微任务队列,打印 microtask in event;

微任务队列为空,存在事件任务队列,执行事件任务队列,打印 event in microtask;

根据如上的运行过程,我们可以得出以下的一个运行结果,这点可以通过运行 Dart 命令得到实际的验证。

flow start
flow end
microtask
event
microtask in event
event in microtask

在这里插入图片描述
一句话概括上面的实践运行结果:每次运行完一个事件后,都会判断微任务和事件任务,在两者都存在时,优先执行完微任务,只有微任务队列没有其他的任务了才会执行事件任务。

疑问2,Dart 运行过程中是否会被事件运行卡住?

答案是会,比如在运行某个微任务,该微任务非常的耗时,会导致其他微任务和事件任务卡住,从而影响到一些实际运行,这里我们可以看如下例子:

import 'dart:async';
void main() {
	print('flow start');  // 执行打印开始
  // 执行判断为事件任务,添加到事件任务队列
	Timer.run((){ 
        for(int i=0; i<1000000000; i++){ // 大循环,为了卡住事件任务执行时间,检查是否会卡住其他任务执行
          if(i == 1000000){
            // 执行判断为微任务,添加到微任务队列
            scheduleMicrotask((){ 
                print('microtask in event'); // 执行微任务,打印微任务标记
            });
          }
        }
        print('event'); // 执行完事件任务,打印执行完事件任务标记
   	});
  // 执行判断为微任务,添加到微任务队列
	scheduleMicrotask((){ 
        print('microtask'); // 执行微任务,打印微任务标记
        // 执行判断为事件任务,添加到事件任务队列
        Timer.run((){
        	print('event in microtask'); // 执行事件任务,打印事件任务标记
        });
    });
	print('flow end'); // 打印结束标记
}

上面这段代码和之前的唯一不同点是在执行第一个事件任务的时候,使用了一个大的 for 循环,从运行结果会看到 event in microtask 和 microtask in event 打印的时间会被 event 的执行所 block 住。从结果分析来看 Dart 中事件运行是会被卡住的,因此在日常编程的时候要特别注意,避免因为某个事件任务密集计算,导致较差的用户操作体验。

Isolate 多线程
上面我们介绍了 Dart 是单线程的,这里说的 Dart 的单线程,其实和操作系统的线程概念是存在一定区别的, Dart 的单线程叫作 isolate 线程,每个 isolate 线程之间是不共享内存的,通过消息机制通信。

我们看个例子,例子是利用 Dart 的 isolate 实现多线程的方式。

import 'dart:async';
import 'dart:isolate';
Isolate isolate;
String name = 'dart';
void main() {
	// 执行新线程创建函数
 	isolateServer();
}
/// 多线程函数
void isolateServer()async{
	// 创建新的线程,并且执行回调 changName 
	final receive = ReceivePort();
	isolate = await Isolate.spawn(changName, receive.sendPort);
	// 监听线程返回信息 
	receive.listen((data){
		print("Myname is $data"); // 打印线程返回的数据
		print("Myname is $name"); // 打印全局 name 的数据
	});
}
/// 线程回调处理函数
void changName(SendPort port){
	name = 'dart isloate'; // 修改当前全局 name 属性
	port.send(name); // 将当前name发送给监听方
	print("Myname is $name in isloate"); // 打印当前线程中的 name
}

以上代码的执行运行流程如下:

import 对应的库;

声明两个变量,一个是 isolate 对象,一个是字符串类型的 name;

执行 main 函数,main 函数中执行 isolateServer 异步函数;

isolateServer 中创建了一个 isolate 线程,创建线程时候,可以传递接受回调的函数 changName;

在 changName 中修改当前的全局变量 name ,并且发送消息给到接收的端口,并且打印该线程中的 name 属性;

isolateServer 接收消息,接收消息后打印返回的数据和当前 name 变量。

根据如上执行过程,可以得出如下的运行结果。

Myname is dart isolate in isolate
Myname is dart isolate
Myname is dart

从运行结果中,可以看到新的线程修改了全局的 name,并且通过消息发送返回到主线程中。而主线程的 name 属性并没有因为创建的新线程中的 name 属性的修改而发生改变,这也印证了内存隔离这点。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值