彻底搞懂 Dart 异步机制(零基础通俗易懂完整版)
前言
本文是 Flutter/Dart 核心基础教程,专门针对异步机制做零基础拆解,摒弃晦涩理论,结合大量可运行案例、生活类比、执行流程分析,帮你彻底吃透 Dart 单线程、事件循环、Future、async/await、任务队列、Isolate 核心知识点。
很多初学者会觉得 Dart 异步难懂,核心原因有两个:
- 不理解单线程 + 事件循环的运行逻辑,混淆多线程与单线程异步的区别;
- 初学阶段没有实际业务场景,无法感知 Future、async/await 的价值。
大家不用焦虑,初学有疑惑是正常的。后续开发网络请求、文件读写、数据解析等功能时,回头复盘本文内容,所有问题都会豁然开朗。
一、Dart 异步核心模型:单线程 + 事件循环
1.1 Dart 是单线程语言
1.1.1 开发中的耗时操作问题
在 Flutter/Dart 开发中,常见耗时操作包括:网络请求、本地文件读写、数据库操作、大量数据解析等。
如果这些耗时操作在主线程同步执行,会直接阻塞主线程,导致页面卡顿、用户点击无响应,这是开发中绝对不允许的情况。
主流编程语言处理耗时操作分为两种方案:
- 多线程方案:Java、C++ 等,手动创建子线程执行耗时任务,通过线程通信传递结果,需处理线程抢占、锁冲突等问题;
- 单线程+事件循环方案:Dart、JavaScript,全程只有一个主线程,依靠事件循环处理所有异步任务,无线程竞争问题。
1.1.2 单线程为什么能实现异步?
很多开发者的核心疑问:单线程同一时间只能做一件事,怎么实现多任务异步执行?
核心答案:依靠非阻塞调用 + 空闲等待。
应用程序的大部分时间都处于空闲等待状态:等待用户点击、等待网络数据返回、等待文件IO完成。这些等待过程不会占用线程执行资源。
阻塞调用 vs 非阻塞调用(生活类比秒懂)
- 阻塞调用:点完外卖后,什么都不做,原地傻傻等待外卖送达,全程卡死;
- 非阻塞调用:点完外卖后,继续工作、刷视频、写代码,仅在空闲时查看外卖是否送达。
Dart 中的网络请求、文件IO 都是非阻塞调用:发起任务后,主线程继续向下执行,不卡顿;任务完成后,通过事件机制回调处理结果。
而串联所有异步任务的核心机制,就是 Event Loop(事件循环)。
1.2 Dart 事件循环(Event Loop)
1.2.1 什么是事件循环?
Dart 单线程的核心调度机制就是事件循环,本质是一个无限循环的任务调度器。
所有用户点击、网络回调、IO 操作、异步任务,都会被封装成事件,放入事件队列(Event Queue)。事件循环会不断从队列中取出任务、执行任务,直到队列清空,全程循环执行。
事件循环伪代码(核心逻辑)
```Plain Text // 模拟事件队列:先进先出 List eventQueue = []; var event;
// 无限循环调度任务 while (true) { if (eventQueue.length > 0) { // 取出队首任务 event = eventQueue.removeAt(0); // 执行任务 event(); } }
### 1\.2\.2 事件循环执行流程实战模拟
以 Flutter 按钮点击发起网络请求为例,拆解事件循环执行逻辑:
```Plain Text
RaisedButton(
child: Text('Click me'),
onPressed: () {
final myFuture = http.get('https://example.com');
myFuture.then((response) {
if (response.statusCode == 200) {
print('请求成功!');
}
});
},
)
执行流程拆解:
- 用户点击按钮,
onPressed回调被加入事件队列,事件循环取出并执行;
- 执行过程中发起网络请求(非阻塞),
onPressed执行完毕,主线程继续空闲;
- 网络请求成功后,
then回调被封装为新事件,加入事件队列;
- 事件循环调度执行
then回调,处理请求结果。
二、Dart 异步核心语法:Future / async / await
Dart 主流异步开发完全依赖 Future 结合 async/await,对应前端 ES6 的 Promise 和 async/await,是 Flutter 网络请求、异步任务的标配写法。
2.1 彻底理解 Future
Future 是 Dart 对异步任务的封装对象,用于表示一个未来才会完成的操作,自带成功、失败回调,完美适配事件循环机制。
2.1.1 同步耗时操作的弊端(阻塞案例)
先看传统同步耗时代码,直观感受阻塞问题:
```Plain Text import "dart:io";
main() { print("main 函数开始"); // 同步耗时操作,阻塞主线程 print(getNetworkData()); print("main 函数结束"); }
// 模拟3秒网络请求 String getNetworkData() { sleep(Duration(seconds: 3)); return "网络请求数据"; }
执行结果:主线程被阻塞,必须等待3秒后,才会执行后续代码
```Plain Text
main 函数开始
(等待3秒)
网络请求数据
main 函数结束
2.1.2 Future 实现异步非阻塞
使用 Future 封装耗时任务,彻底解决主线程阻塞问题:
```Plain Text import "dart:io";
main() { print("main 函数开始"); print(getNetworkData()); print("main 函数结束"); }
// Future 封装异步耗时任务 Future
执行结果:主线程不阻塞,代码顺序执行,耗时任务后台异步执行
```Plain Text
main 函数开始
Instance of 'Future<String>'
main 函数结束
(3秒后无输出,需手动监听结果)
2.1.3 Future 获取结果与异常捕获
通过 .then() 监听成功结果,.catchError() 捕获异常:
```Plain Text import "dart:io";
main() { print("main 函数开始"); var future = getNetworkData(); // 成功回调 future.then((value) { print("请求结果:$value"); }).catchError((error) { // 异常捕获 print("请求失败:$error"); }); print("main 函数结束"); }
Future
### 2\.1\.4 Future 核心特性补充
#### 1)Future 两种状态
- **未完成(uncompleted)**:异步任务执行中(网络请求、延时等待中);
- **已完成(completed)**:任务执行完毕,要么返回成功数据,要么抛出异常。
#### 2)Future 链式调用
`then` 支持链式调用,上一个 then 的返回值,会作为下一个 then 的参数:
```Plain Text
main() {
print("main 函数开始");
getNetworkData()
.then((v1) {
print(v1);
return "二次处理数据";
})
.then((v2) {
print(v2);
return "最终数据";
})
.then((v3) => print(v3));
print("main 函数结束");
}
Future<String> getNetworkData() {
return Future(() {
sleep(Duration(seconds: 3));
return "首次请求数据";
});
}
3)常用 Future 工具 API
Future.value():直接创建一个已完成的成功 Future;
Future.error():直接创建一个已完成的异常 Future;
Future.delayed():延时执行异步任务,最常用的模拟异步场景 API。
2.2 async / await 终极简化异步代码
async/await 是 Future 的语法糖,可以让异步代码写成同步代码的格式,彻底告别繁琐的 then 嵌套,是实际开发的首选写法。
2.2.1 核心规则
- await 只能在 async 修饰的函数中使用;
- async 函数的返回值会自动包装为 Future;
- await 会阻塞当前异步函数,等待任务完成后再执行后续代码(不会阻塞主线程)。
2.2.2 语法改造实战
将上述 Future 案例改造为 async/await 写法:
```Plain Text main() async { print("main 函数开始"); // 等待异步任务完成,获取结果 String result = await getNetworkData(); print("请求结果:$result"); print("main 函数结束"); }
// async 修饰函数,返回值自动封装为 Future Future
优势:代码逻辑清晰、线性执行、无回调地狱,完美适配业务层异步逻辑。
## 2\.3 实战案例:Flutter 读取本地 JSON 文件
结合 Future \+ async/await,实现 Flutter 本地 JSON 解析,贴合实际开发场景:
```Plain Text
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';
// 实体类模型
class Anchor {
String nickname;
String roomName;
String imageUrl;
Anchor({this.nickname, this.roomName, this.imageUrl});
// 工厂方法解析 Map
Anchor.withMap(Map<String, dynamic> map) {
this.nickname = map["nickname"];
this.roomName = map["roomName"];
this.imageUrl = map["roomSrc"];
}
}
// 读取并解析JSON
Future<List<Anchor>> getAnchors() async {
// 1.读取本地JSON文件
String jsonString = await rootBundle.loadString("assets/yz.json");
// 2.解析JSON
final jsonResult = json.decode(jsonString);
// 3.转换为实体对象数组
List<Anchor> anchors = [];
for (Map<String, dynamic> map in jsonResult) {
anchors.add(Anchor.withMap(map));
}
return anchors;
}
三、Dart 异步进阶:任务队列与多核并发
3.1 两大任务队列:微任务队列 + 事件队列
之前提到的事件队列并非唯一队列,Dart 事件循环维护两个优先级不同的队列,这也是面试高频考点:
- 微任务队列(Microtask Queue):优先级最高,优先执行;
- 事件队列(Event Queue):优先级次之,所有IO、点击、计时器、绘制任务都在此队列。
3.1.1 全局代码执行顺序规则
- 优先执行 main 函数同步代码;
- main 执行完毕后,清空所有微任务队列任务;
- 最后依次执行 事件队列所有任务。
3.1.2 创建微任务
通过 scheduleMicrotask 创建微任务:
```Plain Text import "dart:async";
main() { scheduleMicrotask(() { print("执行微任务"); }); }
### 3\.1\.3 Future 任务队列归属规则
- Future 构造函数内的任务:加入**事件队列**;
- 已完成 Future 的 then 回调:加入**微任务队列**;
- 链式 then 回调:顺序加入对应队列,串行执行。
### 3\.1\.4 经典执行顺序面试题(带解析)
```Plain Text
import "dart:async";
main() {
print("main start");
Future(() => print("task1"));
final future = Future(() => null);
Future(() => print("task2"))
.then((_) {
print("task3");
scheduleMicrotask(() => print('task4'));
})
.then((_) => print("task5"));
future.then((_) => print("task6"));
scheduleMicrotask(() => print('task7'));
Future(() => print('task8'))
.then((_) => Future(() => print('task9')))
.then((_) => print('task10'));
print("main end");
}
输出结果
```Plain Text main start main end task7 task1 task6 task2 task3 task5 task4 task8 task9 task10
#### 核心解析
1. 先执行所有同步代码:打印 main start / main end;
2. 清空微任务队列:优先执行 task7;
3. 依次调度事件队列任务,结合 then 微任务、嵌套任务顺序执行;
4. 后注册的嵌套 Future 任务依次排队执行。
## 3\.2 Isolate:利用多核 CPU 实现真正并发
### 3\.2\.1 Isolate 核心概念
Dart 单线程无法利用多核 CPU,而 **Isolate(隔离区)** 是 Dart 的真正并发方案:
- 每个 Isolate 拥有**独立的内存、独立的事件循环、独立任务队列**;
- Isolate 之间不共享内存,无线程资源抢占问题;
- 默认的 UI 线程为 Root Isolate,可手动创建子 Isolate 处理超大耗时计算。
### 3\.2\.2 基础 Isolate 创建
```Plain Text
import "dart:isolate";
main() {
// 创建新的隔离区
Isolate.spawn(computeTask, "多核并发任务");
}
// 子Isolate执行任务
void computeTask(String info) {
print("子Isolate执行:$info");
}
3.2.3 Isolate 双向通信
通过 ReceivePort / SendPort 实现父子 Isolate 消息通信:
```Plain Text import "dart:isolate";
main() async { // 创建消息管道 ReceivePort receivePort = ReceivePort(); // 启动子Isolate并传递通信端口 Isolate isolate = await Isolate.spawn(computeTask, receivePort.sendPort);
// 监听子Isolate返回的消息 receivePort.listen((data) { print("接收子任务结果:$data"); receivePort.close(); isolate.kill(); }); }
void computeTask(SendPort sendPort) { // 向主Isolate发送消息 sendPort.send("超大计算任务执行完成"); }
### 3\.2\.4 Flutter 简化并发:compute 函数
Flutter 封装了 `compute` 工具类,简化 Isolate 创建和通信,开箱即用:
```Plain Text
import 'package:flutter/foundation.dart';
main() async {
// 子线程执行计算任务
int result = await compute(powerNum, 10);
print("计算结果:$result");
}
// 耗时计算方法
int powerNum(int num) {
return num * num;
}
四、全文核心总结
- Dart 异步核心:单线程 + 事件循环 + 非阻塞IO,无多线程竞争;
- Future:封装异步任务,支持成功/失败回调、链式调用,分为完成/未完成两种状态;
- async/await:Future 语法糖,同步写法实现异步逻辑,开发首选;
- 任务优先级:main同步代码 > 微任务队列 > 事件队列;
- Isolate:Dart 真正并发方案,利用多核CPU,适合超大耗时计算,Flutter 可用 compute 快速实现。
(注:部分内容可能由 AI 生成)