等待设备时,界面怎样继续响应
先观察一次延迟结果,再接收连续数据。把 Future、Stream 和回调放在同一条事件顺序中,避免把 async 当成另建任务。
1 · 同步循环为什么会挡住界面
页面需要响应触摸、执行回调并安排画面更新。如果在这条执行路径上写一个长时间不结束的计算循环,后面的事件只能继续等。给函数加 async 并不会自动把长计算搬到另一个线程。
Dart 使用事件循环处理这类异步事件。等待设备、文件等异步结果时,当前异步函数可以暂停,让其他事件有机会处理;可用的后台执行机制称为 isolate,具有自己的执行和内存关系。本支线先把收发与等待写清楚,计算量真有需求时再学习它。
2 · 一个将来的结果:Future
完整 Dart 片段 · 延迟只用于观察顺序
Future<int> readLater() async {
await Future<void>.delayed(const Duration(milliseconds: 20));
return 4000;
}
Future<void> main() async {
print("request");
final result = readLater();
print("continue");
print(await result);
}Future
真正连接设备可能失败,所以用 try/catch 处理失败,用 finally 恢复按钮等清理状态。await 不意味着一定成功,也不意味着调用者之后的页面仍然存在。
3 · 先等到结果,再同步修改页面
State 类的方法片段 · message 与 millivolts 已声明
Future<void> refresh() async {
try {
final value = await readLater();
if (!mounted) return;
setState(() => millivolts = value);
} catch (error) {
if (!mounted) return;
setState(() => message = "Read failed: $error");
}
}等待放在 setState 外面,得到结果后再同步修改状态。mounted 表示这份 State 仍挂在组件树中;等待期间退出页面后,不应再对已销毁页面更新。若界面允许重复点击,还要用“处理中”状态暂时禁用按钮,避免多次操作交错。
4 · 连续到来的结果:Stream
State 内开始接收模拟数据;需 import dart:async
subscription = Stream<int>.periodic(
const Duration(seconds: 1), (i) => 3900 + i % 100,
).listen((value) {
if (!mounted) return;
setState(() => millivolts = value);
});一个 Future 通常对应一次异步结束;Stream 对应陆续出现的事件。listen 登记每次事件到来时要执行的函数,返回一个订阅对象。开始接收应由一次初始化或明确操作触发,不能在 build 中不断增加订阅。
订阅保存在 State 字段,生命周期结束时取消
StreamSubscription<int>? subscription;
@override
void dispose() {
subscription?.cancel();
super.dispose();
}cancel 停止这一份订阅后续事件,mounted 检查负责避免销毁后更新;两者解决不同问题。事件还可能失败或结束,需要时分别处理 onError 和 onDone。
5 · 把等待变成可观察的实验
下载异步示例查看打印顺序,再运行配套 Flutter 小应用。点击开始后每秒出现模拟电压;停止后取消订阅并清除旧读数。模拟值只是界面练习,不代表真实采集。
让 readLater 故意抛出异常,确认失败后还能再次点击。开始接收后退出页面,观察是否出现销毁后更新或遗留定时器错误。解释这些行为与 FreeRTOS 的任务阻塞有什么相似处、执行机制又有哪些区别。
资料与代码
配套文件
先复制到独立练习目录;用法见 支线示例说明。完整 Flutter 小应用见 tutorial/examples/flutter_watch/README.md。