3.2 异步编程:Future, async/await

基础知识

在现代应用程序开发中,异步操作无处不在,例如网络请求、文件读写、数据库查询等。这些操作通常需要一定的时间才能完成,如果同步执行,会导致 UI 阻塞,用户体验下降。Dart 提供了强大的异步编程支持,主要通过 Future 对象和 async/await 关键字来处理异步操作,从而避免 UI 阻塞,提高应用程序的响应性。

1. Future (未来)

Future 对象代表一个异步操作的最终结果或错误。当一个异步操作开始时,它会立即返回一个 Future 对象。这个 Future 对象在操作完成时会“完成”并带有一个值(成功)或一个错误(失败)。

  • 创建 Future

    • Future.value(value):创建一个立即完成并带有指定值的 Future
    • Future.error(error):创建一个立即完成并带有指定错误的 Future
    • Future.delayed(duration, [computation]):创建一个在指定延迟后完成的 Future
  • 处理 Future

    • then():当 Future 成功完成时,then() 方法会执行其回调函数,并接收 Future 的结果。
    • catchError():当 Future 失败并抛出错误时,catchError() 方法会执行其回调函数,并接收错误对象。
    • whenComplete():无论 Future 成功或失败,whenComplete() 方法都会执行其回调函数。通常用于清理资源。
dart
复制代码
void main() {
  print("Start of main");

  // 模拟一个异步操作,2秒后返回一个字符串
  Future<String> fetchData() {
    return Future.delayed(Duration(seconds: 2), () {
      // throw Exception("数据加载失败!"); // 模拟错误
      return "Data fetched successfully!";
    });
  }

  fetchData().then((data) {
    print("Success: $data");
  }).catchError((error) {
    print("Error: $error");
  }).whenComplete(() {
    print("Fetch data operation completed.");
  });

  print("End of main");
}
// 预期输出:
// Start of main
// End of main
// (2秒后)
// Success: Data fetched successfully!
// Fetch data operation completed.

2. asyncawait 关键字

asyncawait 关键字提供了一种更简洁、更易读的方式来编写异步代码,使其看起来像同步代码。

  • async:用于标记一个函数是异步的。一个 async 函数总是返回一个 Future
  • await:只能在 async 函数内部使用。它会暂停 async 函数的执行,直到其后面的 Future 完成。一旦 Future 完成,await 表达式的值就是 Future 的结果。如果 Future 失败,await 会抛出错误。
dart
复制代码
void main() async {
  print("Start of main");

  // 模拟一个异步操作
  Future<String> fetchData() {
    return Future.delayed(Duration(seconds: 2), () {
      return "Data fetched successfully!";
    });
  }

  try {
    String data = await fetchData(); // 等待 fetchData 完成
    print("Success: $data");
  } catch (error) {
    print("Error: $error");
  } finally {
    print("Fetch data operation completed.");
  }

  print("End of main");
}
// 预期输出:
// Start of main
// (2秒后)
// Success: Data fetched successfully!
// Fetch data operation completed.
// End of main

async/await 的优势:

  • 可读性:代码结构更清晰,更接近同步代码的逻辑。
  • 错误处理:可以使用标准的 try-catch-finally 块来处理异步操作中的错误。
  • 流程控制:更容易理解异步操作的执行顺序。

3. 事件循环 (Event Loop)

Dart 是单线程的,但它通过事件循环(Event Loop)和两个队列(事件队列和微任务队列)来实现并发。当 Dart 程序启动时,它会启动一个事件循环。

  • 主线程 (Main Isolate):Dart 代码在主线程上运行。
  • 事件队列 (Event Queue):包含外部事件,如 I/O 操作完成、定时器触发、用户交互等。当主线程空闲时,事件循环会从事件队列中取出事件并执行其回调。
  • 微任务队列 (Microtask Queue):包含更紧急的任务,例如 Future.then() 的回调。微任务队列的优先级高于事件队列。在事件循环从事件队列中取出下一个事件之前,它会清空微任务队列。

理解事件循环有助于理解异步代码的执行顺序和性能。

官方文档链接

Flutter 开发中的应用案例

异步编程在 Flutter 开发中至关重要,因为大多数与外部资源(如网络、文件系统、数据库)的交互都是异步的。Futureasync/await 是 Flutter 应用中处理这些异步操作的基石,它们确保 UI 保持响应,提供流畅的用户体验。

案例:从网络加载图片并显示加载状态

我们将创建一个 Flutter 应用,从网络加载一张图片,并在图片加载过程中显示加载指示器,加载成功后显示图片,加载失败则显示错误信息。这个案例将演示 Futureasync/awaitFutureBuilder 的结合使用。

dart
复制代码
import 'package:flutter/material.dart';

class ImageLoaderScreen extends StatefulWidget {
  const ImageLoaderScreen({super.key});

  @override
  State<ImageLoaderScreen> createState() => _ImageLoaderScreenState();
}

class _ImageLoaderScreenState extends State<ImageLoaderScreen> {
  final String imageUrl = 'https://picsum.photos/id/237/200/300'; // 示例图片 URL
  // final String imageUrl = 'https://invalid.url/image.jpg'; // 模拟加载失败

  Future<void> _loadImage() async {
    // 模拟网络请求延迟
    await Future.delayed(const Duration(seconds: 2));
    // 在实际应用中,这里会使用 http 包进行网络请求
    // 例如:final response = await http.get(Uri.parse(imageUrl));
    // if (response.statusCode == 200) { return true; } else { throw Exception('Failed to load image'); }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('异步图片加载'),
      ),
      body: Center(
        child: FutureBuilder<void>(
          future: _loadImage(), // 传入一个 Future 对象
          builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              // 如果 Future 仍在等待中,显示加载指示器
              return const Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  CircularProgressIndicator(),
                  SizedBox(height: 16),
                  Text('图片加载中...'),
                ],
              );
            } else if (snapshot.hasError) {
              // 如果 Future 完成并带有错误,显示错误信息
              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Icon(Icons.error, color: Colors.red, size: 60),
                  const SizedBox(height: 16),
                  Text('加载失败: ${snapshot.error}', textAlign: TextAlign.center),
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        // 重新触发 Future,再次尝试加载
                      });
                    },
                    child: const Text('重试'),
                  ),
                ],
              );
            } else {
              // 如果 Future 成功完成,显示图片
              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Image.network(
                    imageUrl,
                    width: 200,
                    height: 300,
                    fit: BoxFit.cover,
                    loadingBuilder: (context, child, loadingProgress) {
                      if (loadingProgress == null) return child;
                      return Center(
                        child: CircularProgressIndicator(
                          value: loadingProgress.expectedTotalBytes != null
                              ? loadingProgress.cumulativeBytesLoaded /
                                  loadingProgress.expectedTotalBytes!
                              : null,
                        ),
                      );
                    },
                    errorBuilder: (context, error, stackTrace) {
                      return const Text('图片显示失败');
                    },
                  ),
                  const SizedBox(height: 16),
                  const Text('图片加载成功!'),
                ],
              );
            }
          },
        ),
      ),
    );
  }
}

void main() {
  runApp(const MaterialApp(home: ImageLoaderScreen()));
}

案例分析:

  • Future<void> _loadImage() async { ... }:这是一个异步函数,模拟了从网络加载图片的过程。await Future.delayed(...) 模拟了网络请求的延迟。在实际应用中,这里会使用 httpdio 等网络库进行真正的网络请求。
  • FutureBuilder<void>(future: _loadImage(), builder: ...)FutureBuilder 是 Flutter 中用于处理 Future 的强大 Widget。它接收一个 Future 对象作为 future 参数,并根据 Future 的当前状态(connectionState)来构建不同的 UI。
    • snapshot.connectionState == ConnectionState.waiting:当 _loadImage() 还在执行中时,FutureBuilder 会进入 waiting 状态,此时我们显示 CircularProgressIndicator 和“图片加载中...”的文本。
    • snapshot.hasError:如果 _loadImage() 抛出错误(例如网络请求失败),FutureBuilder 会进入 hasError 状态,此时我们显示错误图标和错误信息,并提供“重试”按钮。
    • snapshot.hasData:如果 _loadImage() 成功完成(尽管这里返回 void,但表示操作成功),FutureBuilder 会进入 hasData 状态,此时我们显示加载成功的图片和文本。
  • Image.network(imageUrl, loadingBuilder: ..., errorBuilder: ...)Image.network 本身也支持异步加载图片,并提供了 loadingBuildererrorBuilder 回调,用于在图片加载中和加载失败时显示不同的 UI。这与 FutureBuilder 的逻辑类似,但更专注于图片加载本身。

这个案例清晰地展示了 Dart 异步编程在 Flutter 应用中的实际应用。通过 Futureasync/await,结合 FutureBuilder 等 Flutter 提供的异步 UI 组件,我们可以优雅地处理耗时操作,确保应用程序的流畅性和响应性,从而提供更好的用户体验。