You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Dart中await内部原理及Flutter异步相关实践问题咨询

关于Dart异步与Flutter本地存储的问题

问题背景

我正在开发一个需要本地存储的Flutter应用,Android中获取文档目录的getApplicationDocumentsDirectory()返回Future<Directory>,每次使用路径都要等待Future完成,示例代码如下:

class DataStructure {
  late Future<Directory> _dir = getApplicationDocumentsDirectory();

  Future<void> read(String fileName) async {
    Directory dir = await _dir;

    // 后续操作
  }
}

我主要写C++,想减少函数推入事件队列的次数来提升性能(推测含await的异步函数会被编译器拆成多个推入队列的“片段”),因此做了实验验证对已完成的Future使用await是否会中断控制流。

实验1:await异步函数的情况

实验代码:

Future<void> voidFuture(String name) async {
  print('$name: voidFuture');
}

Future<void> printSingle(String name, int index) async {
  print('$name: -- index = $index');
}

Future<void> emit(String name, int count) async {
  for (int index = 0; index != count; ++index) {
    await printSingle(name, index);
  }
}

Future<void> task_0() async {
  const String name = 'task_0';
  Future<void> emitFinish = emit(name, 3);
  await voidFuture(name);
  print('$name: output after await');
  await emitFinish;
}

运行输出(Dart SDK 2.18.5 windows_x64):

task_0: -- index = 0
task_0: voidFuture
task_0: -- index = 1
task_0: output after await
task_0: -- index = 2

这个结果符合预期,但修改emit()为直接使用Future构造器后:

实验2:await Future构造器的情况

修改后的emit():

Future<void> emit(String name, int count) async {
  for (int index = 0; index != count; ++index) {
    await Future(() {
      print('$name: -- index = $index');
    });
  }
}

输出变为:

task_0: voidFuture
task_0: output after await
task_0: -- index = 0
task_0: -- index = 1
task_0: -- index = 2

无法理解为什么-- index = 0出现在output after await之后,似乎异步函数比Future构造器创建的Future优先级更高?

实验3:await已完成Future和常量的情况

核心问题:await会等待已完成的Future吗?,实验代码:

Future<String> stringFuture() async {
  return '.';
}

Future<void> task_3() async {
  const String name = 'task_3';
  Future<void> emitFinish = emit(name, 4);
  Future<String> futureString = stringFuture();
  print('$name: before await of futureString');
  await futureString;
  print('$name: 1st await of futureString over');
  await 1;
  print('$name: 1st await of constant over');
  await 2;
  print('$name: 2nd await of constant over');
  await emitFinish;
}

使用第一个版本emit()的输出:

task_3: -- index = 0
task_3: before await of futureString
task_3: -- index = 1
task_3: 1st await of futureString over
task_3: -- index = 2
task_3: 1st await of constant over
task_3: -- index = 3
task_3: 2nd await of constant over

可见即使await常量也会把后续代码推入事件队列;用第二个版本emit()时,所有输出都在task_3()最后一个print之后,也不清楚原因。

已知可以用T? value在Future首次完成后赋值,每次使用前检查是否为null,但有四个问题需要解答:

  1. await关键字内部具体执行逻辑是什么?请给出足够解释上述现象的细节。
  2. 是否可以重写await的默认行为?比如通过重写某个方法?
  3. 多次使用Future值的最佳方式是什么?
  4. 如何在Flutter的欢迎页等待getApplicationDocumentsDirectory()这类异步函数完成后再构建所有组件?

解答

1. await关键字的执行逻辑

Dart的异步基于事件循环和两个优先级不同的队列:

  • 微任务队列:优先级更高,处理短时间完成的异步操作(比如已完成Future的回调、scheduleMicrotask)
  • 事件队列:处理耗时操作(比如IO、UI事件、Future构造器创建的任务)

await的核心逻辑可拆解为:

  1. 执行到await foo时,先检查foo是否为已完成的Future(或可转换为已完成Future的类型,比如常量):
    • 若为已完成的Future:Dart直接获取其值,但仍会把await之后的代码包装成微任务放入微任务队列,当前函数暂停并交出控制权。事件循环会先清空所有微任务,再处理事件队列的任务。
    • 若为未完成的Future:当前函数暂停,给该Future注册回调,当Future完成时,回调(即await之后的代码)会被放入微任务队列等待执行。

解释实验现象:

  • 实验1:printSingle是async函数,调用时同步执行到结束(内部无await),返回已完成的Future。emit里的await printSingle会把下一次循环的代码放入微任务队列。task_0中emitFinish = emit(...)会立即同步打印index=0,微任务队列里有emit的下一次循环。接着await voidFuture(name):voidFuture同步打印后返回已完成Future,print('output after await')被放入微任务队列。事件循环先处理微任务:先执行emit的下一次循环(打印index=1),再执行print('output after await'),最后处理剩下的emit循环(index=2)。
  • 实验2:Future(() { ... })构造器创建的是未完成的Future,其任务被放入事件队列。emit里的await Future(...)会等待事件队列的任务,而task_0中的await voidFuture属于微任务优先级,所以voidFuture的打印、output after await的打印先完成,之后事件循环才处理Future构造器的任务,导致index的打印都在后面。
  • 实验3:await常量时,Dart会把常量包装成已完成的Future,同样会把后续代码放入微任务队列,所以每次await后,emit的微任务先执行,导致index打印穿插在task_3的print之间;用第二个版本emit时,所有Future构造器的任务都在事件队列,要等task_3的所有微任务(包括多次await后的代码)执行完,才会处理事件队列的任务,所以所有index打印在最后。

2. 能否重写await的默认行为?

不能。await是Dart语言的关键字,其行为由语言规范定义,无法通过重写方法或自定义类改变。不过你可以封装工具函数实现类似await的逻辑,但无法替换原生await的行为。

3. 多次使用Future值的最佳方式

推荐以下几种方案:

  • 提前await并缓存结果:在初始化阶段(比如类的构造器、Flutter的initState)await Future,把结果存在变量中,后续直接使用变量:
    class DataStructure {
      late Directory _dir;
    
      Future<void> init() async {
        _dir = await getApplicationDocumentsDirectory();
      }
    
      void read(String fileName) {
        // 直接用_dir,无需await
      }
    }
    
  • 用Future.value()缓存已完成的Future:如果必须保留Future类型,第一次await后把结果重新赋值给原Future变量,后续await时直接获取已完成的值:
    class DataStructure {
      late Future<Directory> _dir = getApplicationDocumentsDirectory();
    
      Future<void> read(String fileName) async {
        final dir = await _dir;
        _dir = Future.value(dir);
        // 使用dir
      }
    }
    
  • 使用AsyncMemoizer:导入package:async/async.dart,用AsyncMemoizer确保异步操作只执行一次,后续调用直接返回缓存结果:
    import 'package:async/async.dart';
    
    class DataStructure {
      final _memoizer = AsyncMemoizer<Directory>();
    
      Future<Directory> get _dir => _memoizer.runOnce(() => getApplicationDocumentsDirectory());
    
      Future<void> read(String fileName) async {
        final dir = await _dir;
        // 使用dir
      }
    }
    

其中提前缓存结果的方式性能最优,避免了后续await的微任务调度开销。

4. Flutter欢迎页等待异步操作完成后再构建组件

有两种常用方式:

  • 使用FutureBuilder:在欢迎页的build方法中返回FutureBuilder,等待异步操作完成后构建主页面:
    class SplashScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return FutureBuilder(
          future: getApplicationDocumentsDirectory(),
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.done) {
              return HomePage(docDir: snapshot.data!);
            } else {
              return Center(child: CircularProgressIndicator());
            }
          },
        );
      }
    }
    
  • 使用StatefulWidget的initState:在initState中执行异步操作,完成后更新状态切换到主页面:
    class SplashScreen extends StatefulWidget {
      @override
      _SplashScreenState createState() => _SplashScreenState();
    }
    
    class _SplashScreenState extends State<SplashScreen> {
      bool _isReady = false;
      Directory? _docDir;
    
      @override
      void initState() {
        super.initState();
        _init();
      }
    
      Future<void> _init() async {
        _docDir = await getApplicationDocumentsDirectory();
        setState(() {
          _isReady = true;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        if (_isReady) {
          return HomePage(docDir: _docDir!);
        } else {
          return Center(child: CircularProgressIndicator());
        }
      }
    }
    

如果需要等待多个异步操作,可用Future.wait([future1, future2])合并等待。


内容的提问来源于stack exchange,提问作者Joseph

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 19:10:19