Flutter:未来不等待

Flutter: Future not waiting

我未来的方法不是在 returns 我的列表之前等待创建我的列表。它 returns 一个空列表,然后正确创建我的列表。

我的代码:

Future<List<Song>> getSongsFromPlaylist(int id) async {
    final playlist = await getPlaylist(id); // get the playlist by its id
    List<Song> list = []; // create an empty list for the songs

    await playlist.songs.forEach((songId) async { // loop through the song-ids from the playlist 
      Song song = await getSong(songId); // get the song by its id
      print(song.id);
      list.add(song); // add the song to the list of songs
      print('list: $list');

    });
    print('returned list: $list');
    return list;
  }

输出:

I/flutter (19367): returned list: []
I/flutter (19367): 1
I/flutter (19367): list: [Instance of 'Song']
I/flutter (19367): 2
I/flutter (19367): list: [Instance of 'Song', Instance of 'Song']
I/flutter (19367): 3
I/flutter (19367): list: [Instance of 'Song', Instance of 'Song', Instance of 'Song']
I/flutter (19367): 4
I/flutter (19367): list: [Instance of 'Song', Instance of 'Song', Instance of 'Song', Instance of 'Song']
I/flutter (19367): 5
I/flutter (19367): list: [Instance of 'Song', Instance of 'Song', Instance of 'Song', Instance of 'Song', Instance of 'Song']

我该如何解决这个问题?谢谢!

你为什么不只用 for 循环来改变 forEach?

for( int i = 0; i< playslist.songs.length; i++) {

}

使用Future.wait并行执行getSong.

Future<List<Song>> getSongsFromPlaylist(int id) async {
  final playlist = await getPlaylist(id);
  return Future.wait<Song>(playlist.songs.map((songId) => getSong(songId)));
}

比 for loop 好多了(只能一首接一首地获取歌曲)。

这段代码可能有助于更好地理解:DartPad

(注意:点击link,dartpad会自动开始运行编译代码。如果点击运行按钮, 你可能会看到一些不想要的行为。所以在执行代码时不要点击 运行 按钮)

很棒而且紧凑。但是如果您需要进一步的操作并且不能使用这样的地图,那么您必须寻求与您的代码类似的解决方案。尽管这些方法有一个警告,即 lists/iterables 不应在执行的期货 concurrently/asynchronously 中进行修改。转到我的答案的末尾以进一步了解这一点。

无论如何按照解决方案,在这种情况下你有两个选择:

选项 A.

使用 Future.wait

以批处理方式遍历列表

在这种情况下,所有迭代将并行执行(理论上)。这确保了最大 performance/concurrency.

它归结为在使用 Future.wait 异步执行它们之前存储期货。

class Song {
  int id;
}

class Playlist {
  List<int> songIds;
}

Future<Playlist> getPlaylist(int id) async {
  return Playlist();
}

Future<Song> getSong(int songId) async {
  return Song();
}

/// Returns list of songs from a `Playlist` using [playlistId]
Future<List<Song>> getSongsFromPlaylist(int playlistId) async {
  /// Local function that populates [songList] at [songListIndex] with `Song` object fetched using [songId]
  Future<void> __populateSongList(
    List<Song> songList,
    int songListIndex,
    int songId,
  ) async {
    // get the song by its id
    Song song = await getSong(songId);
    print(song.id);

    // add the song to the pre-filled list of songs at the specified index to avoid `ConcurrentModificationError`
    songList[songListIndex] = song;

    print(
        'populating list at index $songListIndex, list state so far: $songList');
  } // local function ends here

  // get the playlist object by its id
  final playlist = await getPlaylist(playlistId);

  // create a filled list of pre-defined size to avoid [ConcurrentModificationError](https://api.flutter.dev/flutter/dart-core/ConcurrentModificationError-class.html)
  List<Song> songList = List<Song>.filled(playlist.songIds.length, null);

  // store futures and execute them in a batch manner using [Future.wait](https://api.dart.dev/stable/2.7.1/dart-async/Future/wait.html)
  List<Future<void>> songFutures = [];
  for (int listIndex = 0; listIndex < playlist.songIds.length; listIndex++) {
    final songId = playlist.songIds[listIndex];
    songFutures.add(__populateSongList(songList, listIndex, songId));
  }

  // execute multiple futures concurrently/in parallel
  List<void> songFuturesResult = await Future.wait(songFutures);
  /* ALSO VALID
    List<void> _ = await Future.wait(songFutures);
    await Future.wait(songFutures);
  */

  print('returned list: $songList');

  return songList;
}

选项 B.

顺序遍历列表

在这种情况下,等待每次迭代,直到执行下一次迭代。性能不如选项 A,因为每个 iteration/invocation 都在等待停止控制流,接下来的 iteration/invocation 直到前一个完成后才会开始。在 ConcurrentModificationError

方面,这种方法比前一种方法更安全

仅供参考,此块与之前的选项不同

  // populate songList sequentially -- each iteration/song halted until the previous one finishes execution
  for (int listIndex = 0; listIndex < playlist.songIds.length; listIndex++) {
    final songId = playlist.songIds[listIndex];
    await __populateSongList(songList, listIndex, songId);
  }

但这里还是完整的解决方案:

class Song {
  int id;
}

class Playlist {
  List<int> songIds;
}

Future<Playlist> getPlaylist(int id) async {
  return Playlist();
}

Future<Song> getSong(int songId) async {
  return Song();
}

/// Returns list of songs from a `Playlist` using [playlistId]
Future<List<Song>> getSongsFromPlaylist(int playlistId) async {
  /// Local function that populates [songList] at [songListIndex] with `Song` object fetched using [songId]
  Future<void> __populateSongList(
    List<Song> songList,
    int songListIndex,
    int songId,
  ) async {
    // get the song by its id
    Song song = await getSong(songId);
    print(song.id);

    // add the song to the pre-filled list of songs at the specified index to avoid `ConcurrentModificationError`
    songList[songListIndex] = song;

    print(
        'populating list at index $songListIndex, list state so far: $songList');
  } // local function ends here

  // get the playlist object by its id
  final playlist = await getPlaylist(playlistId);

  // create a filled list of pre-defined size to avoid [ConcurrentModificationError](https://api.flutter.dev/flutter/dart-core/ConcurrentModificationError-class.html)
  List<Song> songList = List<Song>.filled(playlist.songIds.length, null);

  // populate songList sequentially -- each iteration/song halted until the previous one finishes execution
  for (int listIndex = 0; listIndex < playlist.songIds.length; listIndex++) {
    final songId = playlist.songIds[listIndex];
    await __populateSongList(songList, listIndex, songId);
  }

  print('returned list: $songList');

  return songList;
}

说明

Addition/Removal 访问的集合(数组,映射,...)必须在 futures 之外完成,否则在迭代期间并发修改 iterable 将抛出运行时错误。

参考资料

解决方案与讨论

  • Awaiting more than one future -- as a group
  • Question1
  • Question2