flutter的代码运行流程

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 属性的修改而发生改变,这也印证了内存隔离这点。

猜你喜欢

转载自blog.csdn.net/qq_40742949/article/details/108889325