Flutter 基础体系 · 第 28/80 篇。示例基于当前稳定 Flutter 与 Dart 3 语言能力;Android、iOS、桌面和 Web 差异会明确说明。
Dart Future:Event Loop、async/await、错误、超时和并发组合
Future 是 Dart 表示“一次异步结果”的核心抽象。它可能在稍后产生一个值,也可能以错误结束;async/await 只是建立在 Future 之上的语法,而不是另一套并发模型。要正确使用它,必须同时理解四件事:
- 一个 isolate 如何调度同步代码、微任务和事件;
Future如何从未完成变为成功或失败;async/await如何传播值和错误;- 多个异步操作如何组合、超时以及停止或继续执行。
Dart 的异步代码通常不会自动创建线程。单个 isolate 内的 Dart 代码仍然一次只执行一段同步代码;异步的“并发”主要表现为多个 I/O 操作交错等待。需要真正利用多个 CPU 核心执行 Dart 代码时,应使用 isolate,而不是简单地把代码包装成 Future。
Future 是一个一次性结果容器
可以把 Future<T> 抽象成如下状态机:
┌──────────────┐
│ 未完成 │
│ pending │
└──────┬───────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ 完成并产生 T │ │ 完成并产生错误 │
│ value │ │ error │
└──────────────────┘ └──────────────────┘
一个 Future<T>:
- 最多完成一次;
- 完成后不会再次变成未完成;
- 可能成功产生一个
T; - 也可能失败产生一个错误和堆栈;
- 可以有多个监听者,但这些监听者不会改变它的完成结果。
例如:
Future<int> loadCount() {
return Future<int>.delayed(
const Duration(milliseconds: 100),
() => 42,
);
}
调用 loadCount() 时,函数立即返回一个尚未完成的 Future<int>。约 100 毫秒后,该 Future 成功完成,结果为 42。
这里的 100 是调度器至少等待的延迟,不是严格的实时保证。设备负载、浏览器调度、应用是否进入后台以及线程是否被阻塞,都可能使实际回调更晚执行。
Future.value、Future.error 和 Future.delayed
这三个构造器经常用于区分“结果已知”和“稍后完成”:
final a = Future<int>.value(1);
final b = Future<int>.error(StateError('加载失败'));
final c = Future<int>.delayed(
const Duration(seconds: 1),
() => 3,
);
Future.value(1) 表示结果已经确定,但后续的 then 回调仍然按照 Future 的异步语义调度,不会把回调直接插入当前同步调用栈。Future.error 同样表示一个已经确定的失败结果;如果没有监听它,最终可能成为未处理异步错误。
Future.delayed 使用定时器把计算放到将来的事件中。它不是睡眠当前线程,也不会阻塞当前 isolate。
Event Loop:同步代码、微任务和事件队列
一个 Dart isolate 可以近似理解为拥有以下执行过程:
执行当前同步代码
│
▼
清空 microtask queue
│
▼
取出一个 event queue 事件并执行
│
▼
再次清空 microtask queue
│
└───────────────循环
这里有两个重要队列:
- microtask queue(微任务队列):优先级更高,通常用于继续执行 Future 链和
await之后的代码; - event queue(事件队列):用于定时器、I/O、用户输入、系统消息等事件。
每次取出一个事件执行后,事件循环会先处理微任务,直到微任务队列为空,才继续处理下一个事件。因此,微任务可以延迟事件队列中的其他工作。
一个可运行的调度示例
import 'dart:async';
void main() {
print('A');
scheduleMicrotask(() {
print('B: scheduleMicrotask');
});
Future.microtask(() {
print('C: Future.microtask');
});
Future(() {
print('D: Future');
});
Future.delayed(Duration.zero, () {
print('E: Future.delayed');
});
print('F');
}
在常见 Dart VM 实现中,输出顺序是:
A
F
B: scheduleMicrotask
C: Future.microtask
D: Future
E: Future.delayed
逐步分析如下:
print('A')同步执行;- 两个微任务被加入微任务队列;
Future和Future.delayed(Duration.zero)的回调被安排到事件队列;print('F')仍然属于当前同步代码,所以先输出;- 当前同步代码结束后,事件循环清空微任务队列,输出
B和C; - 最后依次处理事件队列中的
D和E。
这个顺序体现的是调度类别,而不是“零延时等同于立即执行”。Duration.zero 仍然需要等当前同步代码和当前批次微任务结束后才可能运行。
微任务饥饿
如果微任务不断创建新的微任务,事件队列可能长时间得不到处理:
import 'dart:async';
void loop() {
scheduleMicrotask(loop);
}
void main() {
loop();
Timer.run(() {
print('这个事件可能长期无法执行');
});
}
loop 每次执行时都会再次加入微任务。由于事件循环必须先清空微任务队列,Timer.run 对应的事件可能一直没有机会执行。这称为微任务饥饿。
因此,微任务适合安排短小的后续工作,不适合承载持续循环、大量计算或无限递归。长计算会阻塞整个 isolate;即使把它放入微任务,也不会变成并行计算。
Future 事件与 Flutter 帧调度
在 Flutter 中,UI isolate 除了处理 Dart 的异步任务,还要参与构建 widget、布局、绘制和平台消息处理。Future 回调可以在这些工作之间运行,但不能把耗时计算变成后台线程。
例如:
Future<void> badWork() async {
// 这段同步计算仍然阻塞当前 isolate
var total = 0;
for (var i = 0; i < 1000000000; i++) {
total += i;
}
print(total);
}
即使调用方式是:
await badWork();
循环也仍然在当前 isolate 执行。await 只能等待 Future,不会自动把同步计算迁移到后台。
在移动端和桌面端,Flutter 的 UI isolate 被阻塞时,表现通常是掉帧、触摸无响应或动画停顿。对于 CPU 密集型任务,需要考虑 isolate;对于网络、文件或数据库等异步 I/O,应使用相应的异步 API。
Flutter Web 的定时器和事件调度还受浏览器主线程、页面后台限速和浏览器事件循环影响;不能把移动端或 Dart VM 上观察到的精确时间顺序当作 Web 上的实时保证。
async 函数:返回 Future 的函数
async 修饰的函数会返回一个 Future:
Future<String> fetchName() async {
return 'Ada';
}
它等价于“异步地完成一个 Future<String>”,而不是返回普通的 String:
final name = fetchName(); // 类型是 Future<String>
正确获取结果的方式是:
final name = await fetchName();
print(name);
或使用 Future 链:
fetchName().then((name) {
print(name);
});
async 函数的调用会开始执行函数体中的同步部分,但它的最终结果通过 Future 暴露。遇到 await 后,函数暂停,把控制权交还给事件循环;等待的 Future 完成后,函数从暂停点继续。
Future<int> calculate() async {
print('1');
final value = await Future<int>.value(10);
print('2');
return value * 2;
}
Future<void> main() async {
print('A');
final future = calculate();
print('B');
final result = await future;
print(result);
}
典型输出为:
A
1
B
2
20
原因是:
- 调用
calculate()后,函数先同步执行到await; Future.value(10)已经有结果,但await后的继续部分仍以异步方式恢复;calculate()返回 Future,调用方继续输出B;- 当前同步代码完成后,
calculate()继续执行,输出2; - 最后外层
await得到20。
“已完成的 Future”不等于“后续代码同步执行”。await 之后的代码应按异步续体理解。
async 函数中的返回和异常
在 async 函数中:
Future<int> succeed() async {
return 10;
}
Future<int> fail() async {
throw StateError('失败');
}
return 10 会使返回的 Future 成功完成;throw 会使返回的 Future 失败完成。调用方如果不等待或监听错误,就可能产生未处理异步错误。
Future<void> main() async {
try {
final value = await succeed();
print(value);
await fail();
} on StateError catch (error, stackTrace) {
print('捕获到:$error');
print(stackTrace);
}
}
try/catch 必须包住实际的 await,这样错误才会沿着当前异步调用链返回到捕获点。
await 的错误传播
await future 有两种结果:
- Future 成功:表达式的值就是 Future 的结果;
- Future 失败:
await位置抛出对应错误,控制流跳到匹配的catch。
Future<int> readValue() async {
throw FormatException('数据格式不正确');
}
Future<void> run() async {
try {
final value = await readValue();
print(value); // 不会执行
} on FormatException catch (error) {
print('格式错误:$error');
} catch (error) {
print('其他错误:$error');
}
}
on FormatException 用于按类型筛选错误,catch (error, stackTrace) 可以同时获得错误对象和堆栈。生产代码通常应保留原始堆栈,因为错误类型本身不足以定位失败发生的位置。
then、catchError 和错误链
Future 链也会传播成功值和错误:
Future<void> main() {
return Future<int>.value(5)
.then((value) {
return value * 2;
})
.then((value) {
print(value); // 10
})
.catchError((error, stackTrace) {
print('处理错误:$error');
});
}
then 回调返回普通值时,链继续成功;返回 Future 时,链会等待该 Future,这叫作 Future 链的扁平化:
Future<int> step1() async => 1;
Future<int> step2(int value) async => value + 1;
Future<int> chain() {
return step1().then((value) {
return step2(value); // 返回 Future<int>,不会变成 Future<Future<int>>
});
}
如果 then 回调抛出异常,后面的成功回调会被跳过,错误继续寻找后续的错误处理器。
不过,复杂分支使用 try/catch 往往更容易验证错误范围:
Future<void> loadPage() async {
try {
final user = await loadUser();
final posts = await loadPosts(user.id);
print(posts);
} catch (error, stackTrace) {
print('页面加载失败:$error');
print(stackTrace);
}
}
catchError 的回调返回值必须与 Future 链要求的类型兼容;否则错误处理器本身可能再次产生类型错误。使用 try/catch 可以减少这类返回类型陷阱。
错误处理不是取消操作
捕获错误只改变错误的传播方式,不会自动撤销已经开始的操作:
Future<void> example() async {
try {
await request();
} catch (error) {
print('请求失败');
}
}
如果 request() 内部已经向服务器发送数据,外层 catch 不会把网络请求回滚。是否能取消,取决于具体 API 是否提供取消机制,例如 HTTP 客户端的取消令牌、流订阅的 cancel 或自定义的取消状态。
这一区分在超时场景尤其重要。
Zone:未处理异步错误的边界
Dart 的 Zone 可以为一段异步执行树提供上下文和错误处理边界。runZonedGuarded 常用于捕获没有被 Future 链显式处理的异步错误:
import 'dart:async';
void main() {
runZonedGuarded(() {
Future<void>.delayed(const Duration(milliseconds: 10), () {
throw StateError('后台任务失败');
});
}, (error, stackTrace) {
print('Zone 捕获:$error');
print(stackTrace);
});
}
这里的异常发生在稍后的异步回调中,已经不在 main 的同步 try/catch 调用栈内,但仍处于该 Zone 的异步上下文中,因此可以由 Zone 错误处理器接收。
应区分三类问题:
- 同步异常:当前调用栈中的
try/catch可以捕获; - Future 失败:通过
await、then或错误处理器捕获; - 未处理异步错误:可能进入 Zone 的错误处理器,或由运行时报告。
在 Flutter 中,框架错误报告机制还包括 Flutter 自己的错误回调;Zone 不能替代对 Flutter 框架错误、平台通道错误和业务 Future 错误的分别处理。
超时:Future.timeout 做了什么
Future.timeout 为“等待结果”设置一个时间上限:
Future<String> requestWithTimeout() {
return request().timeout(
const Duration(seconds: 3),
onTimeout: () => '使用缓存',
);
}
它的语义是:
- 如果原 Future 在 3 秒内成功或失败,使用原结果;
- 如果 3 秒先到,且没有提供
onTimeout,返回的 Future 失败,通常是TimeoutException; - 如果提供
onTimeout,可以返回替代值或另一个 Future。
Future<String> request() async {
await Future<void>.delayed(const Duration(seconds: 5));
return '服务器结果';
}
Future<void> main() async {
final result = await request().timeout(
const Duration(seconds: 1),
onTimeout: () => '超时降级结果',
);
print(result); // 超时降级结果
}
超时不会取消底层 Future
上例在 1 秒时返回了“超时降级结果”,但 request() 本身仍可能在 5 秒后完成。timeout 只是改变外部等待者看到的 Future 结果,不会自动停止原始操作。
可以用时间关系表示:
原操作开始 deadline 原操作完成
│──────────────│──────────────────────────│
│ │ │
│ timeout Future 完成 │
│ 返回降级值或 TimeoutException │
│ │
└──────底层操作仍可能继续执行──────────────┘
若原操作完成时间为 Toperation,超时期限为 Tdeadline:
Toperation < Tdeadline:观察者得到原操作结果;Toperation >= Tdeadline:观察者得到超时结果;- 但无论哪种情况,底层操作是否停止由底层 API 决定。
因此,超时后的副作用必须谨慎处理。例如请求已经发出时,不能因为客户端超时就假定服务器没有执行。涉及写操作时,应使用幂等键、状态查询或服务端去重机制。
Future 的并发组合
创建多个 Future 通常意味着多个操作可以同时进行,但“同时”要根据操作性质理解:
- 网络请求可以在等待 I/O 时交错进行;
- 文件和数据库异步操作可以重叠等待;
- CPU 密集型 Dart 代码不会因为返回 Future 就并行执行;
- 多个任务都在同一个 isolate 中时,仍然不会同时执行 Dart 指令。
顺序执行:后一个依赖前一个
Future<void> sequential() async {
final user = await loadUser();
final posts = await loadPosts(user.id);
print(posts);
}
第二个请求必须使用第一个请求的结果,因此顺序是数据依赖导致的,而不是 await 本身“擅自限制了所有并发”。
独立操作:先启动,再等待
如果两个操作相互独立,可以先保存 Future:
Future<void> parallel() async {
final userFuture = loadUser();
final settingsFuture = loadSettings();
final user = await userFuture;
final settings = await settingsFuture;
print('$user / $settings');
}
loadUser() 和 loadSettings() 在各自被调用时就开始执行;第二个 await 不会等到第一个 await 完成后才启动设置加载。
把代码写成下面这样,语义不同:
Future<void> notParallel() async {
final user = await loadUser();
final settings = await loadSettings();
print('$user / $settings');
}
这里 loadSettings() 只有在 loadUser() 完成后才被调用。
Future.wait:等待全部任务
Future.wait 用于把多个 Future 组合为一个 Future:
Future<void> loadDashboard() async {
final results = await Future.wait<Object>([
loadUser(),
loadSettings(),
loadNotifications(),
]);
final user = results[0];
final settings = results[1];
final notifications = results[2];
print('$user, $settings, $notifications');
}
它有三个重要性质:
- 返回的 Future 只有在所有输入 Future 成功后才成功;
- 返回列表的顺序与输入 Future 的顺序一致,不取决于完成先后;
- 任意输入失败时,组合 Future 会失败。
例如三个任务的完成顺序为:
输入顺序: A B C
完成顺序: B C A
结果列表: [A结果, B结果, C结果]
Future.wait 默认不是遇到第一个错误就立即向调用方完成;eagerError: true 可以让组合 Future 在第一个错误出现时尽快失败:
final values = await Future.wait<String>(
[
loadPrimary(),
loadSecondary(),
],
eagerError: true,
);
但 eagerError: true 仍然不会取消其他 Future。它只改变“组合结果何时向外报告错误”,不能停止已经开始的任务。
cleanUp 可用于在整体失败时处理已经成功返回、但最终不会交给调用方的资源:
final files = await Future.wait<FileHandle>(
[
openFileA(),
openFileB(),
],
cleanUp: (file) {
file.close();
},
);
资源释放函数本身不应再抛错;否则可能造成额外的未处理异步错误。是否使用 cleanUp 取决于结果是否需要关闭、删除或归还,普通值不需要人为清理。
Future.wait 的失败路径
假设:
A:成功,返回资源 a
B:失败
C:仍在执行
当 Future.wait([A, B, C]) 失败时:
A的结果不会作为整体结果返回;B的错误成为组合失败;C是否继续执行,取决于它自身是否可取消;- 组合失败不等于所有任务都已经停止。
因此,批量任务需要分别设计“错误传播”和“资源回收”,不能仅依赖 try/catch。
Future.any:第一个完成者获胜
Future.any 返回第一个完成的 Future 的结果,无论这个结果是成功还是错误:
Future<String> fastestSource() {
return Future.any<String>([
loadFromCache(),
loadFromLocalDatabase(),
loadFromNetwork(),
]);
}
如果缓存先成功,调用方得到缓存;如果网络请求先失败,而其他来源稍后才成功,Future.any 也会先以错误结束。
这和“第一个成功者”不同。若需求是“忽略失败,直到某个来源成功”,不能直接使用 Future.any,需要为每个任务单独捕获错误,并定义全部失败时的结果:
Future<String> firstSuccessful(
List<Future<String> Function()> factories,
) async {
final errors = <Object>[];
for (final factory in factories) {
try {
return await factory();
} catch (error) {
errors.add(error);
}
}
throw StateError('所有来源都失败:$errors');
}
上面的实现是顺序尝试,不是并发尝试。并发“第一个成功者”需要同时启动所有任务,并维护成功计数和全部失败状态;还必须考虑失败任务和胜出后剩余任务的取消问题。Dart 核心 Future 没有通用的自动取消协议,因此这通常需要依赖具体库或自定义取消机制。
顺序批处理和并发批处理
下面两个实现处理同一批 ID,但并发行为不同:
Future<void> processSequentially(List<int> ids) async {
for (final id in ids) {
await process(id);
}
}
Future<void> processConcurrently(List<int> ids) async {
await Future.wait(ids.map(process));
}
顺序版本的总等待时间近似为:
其中 T_i 是第 i 个任务的耗时。
并发版本在资源允许时近似为:
但这个公式只描述理想等待时间,实际还会受连接池、服务器限流、内存、CPU 和任务调度影响。并发启动几千个网络请求可能比适度并发更慢,还可能触发服务端拒绝或客户端资源耗尽。
一个简单的并发上限实现如下:
Future<List<R>> mapWithLimit<T, R>(
Iterable<T> items,
int limit,
Future<R> Function(T item) operation,
) async {
if (limit <= 0) {
throw ArgumentError.value(limit, 'limit', '必须大于 0');
}
final values = items.toList();
final results = List<R?>.filled(values.length, null);
var nextIndex = 0;
Future<void> worker() async {
while (true) {
final index = nextIndex++;
if (index >= values.length) {
return;
}
results[index] = await operation(values[index]);
}
}
final workerCount = limit < values.length ? limit : values.length;
await Future.wait(List.generate(workerCount, (_) => worker()));
return results.cast<R>();
}
这里有两个关键点:
nextIndex++在同一个 isolate 的同步代码中执行,不会被另一个 Dart 线程同时打断;results[index]使用输入索引写入,因此结果顺序仍与输入顺序一致;- 任意一个
operation失败时,Future.wait会失败,但已经启动的其他任务仍可能继续。
这个示例适用于控制异步操作数量,不适用于把 CPU 密集型工作变成并行计算。
Future.wait、Future.any 和超时的组合
下面是一个带整体超时的批量加载:
Future<List<String>> loadAll() async {
return Future.wait<String>([
loadA(),
loadB(),
loadC(),
]).timeout(
const Duration(seconds: 5),
);
}
其失败路径至少有三种:
- 所有任务在 5 秒内完成:返回完整列表;
- 某个任务先失败:整体 Future 失败;
- 5 秒先到:整体 Future 超时,但未完成的底层任务可能继续执行。
如果业务需要“部分成功”,就不能直接把全部任务交给 Future.wait,而应把每项结果封装成成功或失败状态:
sealed class Result<T> {
const Result();
}
final class Success<T> extends Result<T> {
const Success(this.value);
final T value;
}
final class Failure<T> extends Result<T> {
const Failure(this.error, this.stackTrace);
final Object error;
final StackTrace stackTrace;
}
Future<Result<T>> capture<T>(Future<T> future) async {
try {
return Success<T>(await future);
} catch (error, stackTrace) {
return Failure<T>(error, stackTrace);
}
}
Future<List<Result<String>>> loadPartially() {
return Future.wait<Result<String>>([
capture(loadA()),
capture(loadB()),
capture(loadC()),
]);
}
这样,组合 Future 本身通常成功完成,但每个元素分别描述自己的成功或失败。它适合仪表盘、首页多个独立模块等场景;对于必须全部成功的事务,不应把失败转换成普通结果后悄悄忽略。
常见误解及其失败表现
把 Future 当作后台线程
Future<void> doHeavyWork() async {
for (var i = 0; i < 1000000000; i++) {
// CPU 密集型计算
}
}
async 不会创建线程。调用它可能让 UI 卡顿,因为循环在当前 isolate 内同步执行。诊断时可以观察 Flutter DevTools 的帧耗时和 CPU profile;如果主 isolate 在长时间运行单段同步代码,问题不是缺少 await,而是需要拆分工作、优化算法或迁移到 isolate。
用 Future.delayed 模拟取消
await Future.delayed(const Duration(seconds: 1));
这个 Future 没有取消接口。即使调用方不再等待,它也可能继续完成。Future.delayed 适合延迟重试、演示和定时流程,不是通用取消工具。
以为超时后网络请求已经停止
try {
await sendPayment().timeout(const Duration(seconds: 3));
} on TimeoutException {
print('客户端超时');
}
这里只能证明“客户端等待超过 3 秒”,不能证明服务器没有收到或没有处理支付请求。支付、下单、上传等副作用操作必须设计幂等和查询机制。
误用 Future.wait 获得并发
for (final id in ids) {
await load(id);
}
这段代码不是并发,因为下一次 load 的调用发生在上一次 await 成功之后。需要并发时,应先创建 Future:
final futures = ids.map(load).toList();
final values = await Future.wait(futures);
但这会一次性启动所有操作。数据量大时,应使用并发上限,而不是盲目扩大并发度。
在循环中递归安排微任务
这种代码可能导致定时器、I/O 和 Flutter 帧事件无法及时处理:
void keepScheduling() {
scheduleMicrotask(keepScheduling);
}
如果需要让出事件循环,应重新设计任务边界,必要时使用事件队列或拆分工作;如果目标是避免 UI 阻塞,仅仅切换到微任务并不能解决 CPU 占用问题。
生命周期和平台边界
Future 没有统一的“页面生命周期感知”能力。一个页面销毁后,它发起的 Future 仍可能完成。Flutter 代码需要在结果返回时确认状态对象仍然有效:
Future<void> loadState() async {
final data = await repository.load();
if (!mounted) {
return;
}
setState(() {
// 使用 data 更新界面
});
}
这里的 mounted 是 State 的生命周期状态,不是 Future 的取消机制。它只能避免在已卸载的 widget 上更新 UI,不能阻止网络、文件或数据库操作继续执行。
在 Android、iOS 和桌面平台,应用进入后台可能被暂停、限制甚至直接终止;挂起期间的定时器和 Future 不提供“必定按墙上时间完成”的保证。Web 还可能受到浏览器后台标签页限速。需要可靠完成的任务,应交给平台后台任务机制或服务端,而不是只依赖当前 Dart isolate 中的 Future。
Dart VM 和 Flutter 原生平台通常可以使用 isolate 执行隔离计算;Flutter Web 的 isolate 能力、线程模型和可用 API 受浏览器环境限制。不能仅因为代码在移动端可通过 isolate 并行执行,就假设同一代码在 Web 上拥有相同的线程行为。
诊断异步问题的路径
遇到 Future 相关问题时,可以按执行路径检查,而不是只在末端打印“加载失败”:
- 确认操作何时启动:是调用函数时启动,还是第一次
await后才启动; - 确认是否阻塞当前 isolate:查找长循环、同步文件操作、复杂 JSON 解析和大对象转换;
- 记录开始、成功、失败和超时时间:区分底层操作慢、组合等待慢和 UI 更新晚;
- 保留错误堆栈:使用
catch (error, stackTrace),不要只记录error.toString(); - 检查错误是否被吞掉:
catchError是否返回了错误类型不兼容的值,是否把失败转换成了无意义的默认值; - 检查超时后的副作用:底层 Future 是否仍在运行,是否可能重复提交;
- 检查生命周期:Future 完成时,页面、State 或请求上下文是否仍然有效;
- 检查并发数量:
Future.wait是否一次启动了过多任务,服务端和客户端是否支持这样的负载。
可以给异步操作附加上下文:
Future<T> traced<T>(
String name,
Future<T> Function() operation,
) async {
final started = DateTime.now();
try {
final value = await operation();
print('$name succeeded: ${DateTime.now().difference(started)}');
return value;
} catch (error, stackTrace) {
print('$name failed: $error');
print(stackTrace);
rethrow;
}
}
rethrow 会保持当前错误的传播方式和原始堆栈语义;重新 throw error 可能改变堆栈起点,不适合无必要地替换原始错误。
核心边界
Future 解决的是“一次异步结果如何表示和组合”,不是所有并发问题的统一答案:
- 它不自动提供线程;
- 它不自动取消底层操作;
- 它不保证精确的时间;
- 它不保证应用生命周期跨越后台或进程终止;
- 它不替代资源释放、幂等设计和服务端事务;
- 它不让 CPU 密集型同步代码变得非阻塞。
正确的推导顺序是:先确定任务是在当前 isolate 中同步执行、异步等待 I/O,还是需要另一个 isolate;再确定 Future 的成功和失败边界;随后选择顺序等待、Future.wait、Future.any 或带并发上限的组合;最后单独设计超时、取消、生命周期和副作用处理。只有把这些边界分开,async/await 才能准确表达程序的数据依赖和故障路径。
系列导航与关联阅读
- 系列入口:Flutter 完整学习路线:从 Dart 与 Widget 到多端架构和应用发布
- 上一篇:Dart Record 与模式匹配:解构、switch、封闭建模和返回值
- 下一篇:Dart Stream:单订阅、广播、转换、背压边界和取消
官方资料
本文依据 Flutter 与 Dart 官方文档重新梳理;正文与示例由 WR BLOG 编写。

评论
0 条讨论