flutter async/await 详解

一、核心概念

1. 为什么需要 async/await?

Flutter 是单线程事件循环模型(类似 JavaScript),所有 UI 代码都在主线程(Isolate)执行。如果执行耗时操作(网络请求、文件读写、数据库查询),会阻塞 UI 导致卡顿。

async/await 是 Dart 提供的语法糖,让你用"同步的写法"处理"异步的逻辑",代码更易读。

2. 三个关键角色

概念 含义 类比
Future<T> 表示将来会完成的异步操作,成功返回 T,失败抛异常 Android 的 CompletableFuture
async 标记函数为异步函数,函数内部可以使用 await 声明"这个函数里有异步操作"
await 等待一个 Future 完成,并获取其结果 "在这里停下来,等结果回来再继续"

二、基础用法

1. 定义异步函数

复制代码
// async 函数必须返回 Future(或 void)
Future<String> fetchUserData() async {
  // await 会暂停此处,等待 Future 完成,但不会阻塞 UI 线程
  final response = await http.get(Uri.parse('https://api.example.com/user'));
  
  if (response.statusCode == 200) {
    return response.body; // 自动包装成 Future<String>
  } else {
    throw Exception('请求失败'); // 抛异常
  }
}

2. 调用异步函数

复制代码
void getData() async {
  try {
    print('开始请求...');
    String data = await fetchUserData();  // 等待完成
    print('收到数据: $data');              // 完成后执行
  } catch (e) {
    print('出错了: $e');
  }
}

执行流程图解:

复制代码
主线程: [print开始] → [发起请求] → [挂起,执行其他UI任务] → [请求完成] → [print收到数据]
                    ↑___________________________________________|
                           await 期间,事件循环继续处理其他事件

三、与 Android 的对比

场景 Android (Kotlin) Flutter (Dart)
异步结果 suspend + Deferred async + Future
等待结果 val result = deferred.await() var result = await future
线程切换 withContext(Dispatchers.IO) 自动在事件循环中调度,Isolate 间用 compute()
并发执行 async { } Future.wait([...])

四、进阶用法

1. 并行执行多个异步任务

复制代码
// ❌ 错误:串行执行,总时间 = 3s + 3s + 3s = 9s
final a = await fetchA();
final b = await fetchB();
final c = await fetchC();

// ✅ 正确:并行执行,总时间 ≈ 3s
final results = await Future.wait([
  fetchA(),
  fetchB(),
  fetchC(),
]);
// results = [a结果, b结果, c结果]

2. 超时控制

复制代码
try {
  final data = await fetchData().timeout(Duration(seconds: 5));
} on TimeoutException {
  print('请求超时');
}

3. 延迟执行

复制代码
await Future.delayed(Duration(seconds: 2)); // 延迟2秒

4. 同步执行异步代码(谨慎使用)

复制代码
// 在 initState 等同步方法中需要异步数据
@override
void initState() {
  super.initState();
  // ❌ 不能直接用 await
  // final data = await fetchData();
  
  // ✅ 启动异步任务
  _loadData();
}

void _loadData() async {
  final data = await fetchData();
  setState(() {
    _data = data;
  });
}

五、错误处理

1. try-catch 捕获异常

复制代码
Future<void> loadData() async {
  try {
    final data = await fetchData();
  } on SocketException {
    print('网络错误');
  } on FormatException {
    print('数据格式错误');
  } catch (e, stackTrace) {
    print('未知错误: $e');
    print(stackTrace); // 打印堆栈
  } finally {
    print('无论成败都会执行');
  }
}

2. Future 的链式错误处理

复制代码
fetchData()
  .then((value) => print(value))
  .catchError((e) => print('错误: $e'))
  .whenComplete(() => print('结束'));

六、Flutter 中的典型场景

1. 网络请求 + UI 更新

复制代码
class _MyPageState extends State<MyPage> {
  List<User> _users = [];
  bool _isLoading = false;

  Future<void> _fetchUsers() async {
    setState(() => _isLoading = true);
    
    try {
      final response = await http.get(Uri.parse('https://api/users'));
      final List<dynamic> json = jsonDecode(response.body);
      
      setState(() {
        _users = json.map((e) => User.fromJson(e)).toList();
      });
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('加载失败: $e')),
      );
    } finally {
      setState(() => _isLoading = false);
    }
  }
}

2. 文件读写

复制代码
Future<String> readFile() async {
  final directory = await getApplicationDocumentsDirectory();
  final file = File('${directory.path}/data.txt');
  return await file.readAsString();
}

3. SharedPreferences

复制代码
Future<void> saveToken(String token) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('token', token);
}

七、常见坑

❌ 坑 1:在 build 方法里直接 await

复制代码
// 错误!build 必须是同步的
@override
Widget build(BuildContext context) {
  final data = await fetchData(); // ❌ 编译错误
  return Text(data);
}

// 正确:用 FutureBuilder
@override
Widget build(BuildContext context) {
  return FutureBuilder<String>(
    future: fetchData(),
    builder: (context, snapshot) {
      if (snapshot.connectionState == ConnectionState.waiting) {
        return CircularProgressIndicator();
      }
      if (snapshot.hasError) {
        return Text('错误: ${snapshot.error}');
      }
      return Text(snapshot.data!);
    },
  );
}

❌ 坑 2:忘记 await

复制代码
// 错误:没有 await,函数会立即返回,不等待完成
void save() async {
  final prefs = SharedPreferences.getInstance(); // 少了 await!
  prefs.setString('key', 'value'); // 这里 prefs 是 Future<SharedPreferences>
}

// 正确
void save() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('key', 'value');
}

❌ 坑 3:setState 时 Widget 已销毁

复制代码
Future<void> loadData() async {
  final data = await fetchData();
  
  // 如果用户在这时候退出了页面,setState 会报错
  if (mounted) {  // ✅ 先检查 Widget 是否还在树中
    setState(() {
      _data = data;
    });
  }
}

八、总结

要点 说明
async 修饰函数,内部可用 await
await 等待 Future 完成,挂起当前函数但不阻塞线程
Future 异步操作的"承诺",类似 Promise
并行 用 Future.wait([])
错误 用 try-catch 或 .catchError()
UI 更新 异步操作后用 setState,记得检查 mounted

async/await 的本质是 Dart 编译器帮你把代码转换成 Future 的 then/catchError 链式调用,让你写异步代码像写同步代码一样直观。


flutter的async /await 和 android 的 async / await 对比

这是一个非常好的深入问题。虽然两者表面上都叫 async/await,但 Flutter (Dart) 和 Android (Kotlin) 的实现机制、线程模型和设计理念差异巨大。下面从多个维度详细对比:


一、核心定位对比

维度 Flutter (Dart) Android (Kotlin)
本质 单线程事件循环的语法糖 多线程协程的语法糖
底层机制 Future + _SuspendState 状态机 Continuation + CPS 状态机
线程关系 async/await 不创建线程,只在同一线程挂起/恢复 async/await 可跨线程调度(Dispatchers)
并发单元 Future(将来值) Deferred<T>(可等待的协程任务)
结构化并发 ❌ 无内置作用域,需手动取消 ✅ CoroutineScope 自动管理生命周期

二、语法写法对比

Dart (Flutter)

复制代码
// async 函数返回 Future<T>
Future<String> fetchUser() async {
  final response = await http.get(Uri.parse('/user'));  // await 等待 Future
  return response.body;
}

// 并行执行
Future<void> loadAll() async {
  final results = await Future.wait([
    fetchUser(),
    fetchOrders(),
  ]);
}

Kotlin (Android)

复制代码
// suspend 函数,不直接返回 Future/Deferred
suspend fun fetchUser(): String {
    val response = api.getUser()  // suspend 函数可直接调用
    return response.body
}

// 并行执行(需要 CoroutineScope)
suspend fun loadAll() = coroutineScope {
    val user = async { fetchUser() }    // async 启动新协程,返回 Deferred
    val orders = async { fetchOrders() }
    val result = "${user.await()} + ${orders.await()}"  // await 等待 Deferred
}

三、底层实现差异(关键!)

Dart:基于事件循环的状态机

Dart 的 async/await 编译后本质上还是 Future.then 的链式调用。

  • 遇到 await 时,函数被挂起 (suspend),当前状态保存到 _SuspendState

  • 事件循环继续处理其他任务(UI 渲染、点击事件等)

  • Future 完成后,通过微任务(microtask)恢复执行

关键特点 :全程在**同一个 Isolate(单线程)**内完成,没有线程切换开销。

Kotlin:基于 CPS 的协程状态机

Kotlin 的 suspend 函数编译后会被改写成Continuation Passing Style:

复制代码
// 你写的:
suspend fun getUser(): User?

// 编译后类似:
fun getUser(continuation: Continuation<*>): Any?
  • Continuation 保存函数状态、局部变量、调用上下文

  • 多个 Continuation 形成链表,支持挂起和恢复

  • 可以通过 withContext(Dispatchers.IO) 自由切换线程


四、线程模型对比(最大区别!)

场景 Dart (Flutter) Kotlin (Android)
默认执行线程 主 Isolate(单线程) 调用方所在线程(通常是主线程)
IO 操作 单线程事件循环,IO 通过 C++ 层非阻塞实现 withContext(Dispatchers.IO) 切换到 IO 线程池
耗时计算 会阻塞 UI!必须用 compute() 放到 Isolate withContext(Dispatchers.Default) 切换到计算线程池
线程切换 ❌ async/await 本身不支持 ✅ Dispatchers.Main/IO/Default 自由切换

形象比喻

  • Dart :像一家只有一个服务员的餐厅 。服务员(事件循环)接待客人 A,A 点菜(await),服务员去服务 B,菜好了(Future 完成)服务员回来继续服务 A。始终只有一个人。

  • Kotlin :像一家有多个服务员的餐厅 。主服务员(主线程)接待客人 A,A 需要做菜时,主服务员把任务转给厨房服务员(IO 线程),菜好了厨房服务员通知主服务员端给 A。可以有多个人协作。


五、并行执行对比

Dart:用 Future.wait

复制代码
// 同时启动,等待全部完成
final results = await Future.wait([
  fetchA(),  // 返回 Future
  fetchB(),
  fetchC(),
]);

Kotlin:用 async + await

复制代码
coroutineScope {
    val a = async { fetchA() }  // 立即启动,返回 Deferred
    val b = async { fetchB() }
    val c = async { fetchC() }
    
    // 等待全部完成
    val results = listOf(a.await(), b.await(), c.await())
}

⚠️ 注意 :Kotlin 的 async { }.await() 是串行的,async { } 单独调用才是并行的。


六、生命周期与取消机制

特性 Dart Kotlin
作用域 无内置作用域概念 lifecycleScope, viewModelScope
自动取消 ❌ 需手动管理(如 CancelableOperation) ✅ 组件销毁时自动取消协程
结构化并发 ❌ 子任务异常不会自动取消兄弟任务 ✅ 一个子任务失败,兄弟任务自动取消

Kotlin 示例(结构化并发优势)

复制代码
class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            // ViewModel 清除时自动取消,避免内存泄漏
            val data = fetchData()
        }
    }
}

Dart 示例(需手动管理)

复制代码
class _MyPageState extends State<MyPage> {
  StreamSubscription? _sub;
  
  @override
  void dispose() {
    _sub?.cancel();  // 必须手动取消
    super.dispose();
  }
}

七、错误处理对比

方式 Dart Kotlin
try-catch ✅ 支持 ✅ 支持
异常传播 Future 异常需 await 才能捕获 协程异常自动向上传播,可集中处理
SupervisorJob 无对应概念 可隔离子协程异常,不影响兄弟协程

八、总结:什么时候用什么?

场景 推荐方案
Flutter 开发 用 Dart async/await,记住它是单线程的 ,耗时计算用 compute()
Android 开发 用 Kotlin Coroutines,善用 Dispatchers.IO 做网络/数据库,Dispatchers.Default 做计算
跨平台逻辑层 Kotlin Multiplatform 的协程可在 Android/iOS 共享,但 iOS 端是单线程模拟

一句话总结

Dart 的 async/await = 单线程事件循环里的"假装等待"(挂起/恢复) Kotlin 的 async/await = 多线程协程池里的"真正调度"(可跨线程)

两者都是语法糖,但背后的"糖衣"包裹的完全是不同的实现:Dart 是事件循环驱动 ,Kotlin 是线程池调度驱动。

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui