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 将抛出运行时错误。
参考资料
- ConcurrentModificationError Class
- 请参阅
List
和 Iterable
class 中带有关键字 "concurrent" 的注释,以获取列表 class and Iterable class 中的更多信息。
解决方案与讨论
- Awaiting more than one future -- as a group
- Question1
- Question2
我未来的方法不是在 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会自动开始运行编译代码。如果点击运行按钮, 你可能会看到一些不想要的行为。所以在执行代码时不要点击 运行 按钮)
无论如何按照解决方案,在这种情况下你有两个选择:
选项 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 将抛出运行时错误。
参考资料
- ConcurrentModificationError Class
- 请参阅
List
和Iterable
class 中带有关键字 "concurrent" 的注释,以获取列表 class and Iterable class 中的更多信息。
解决方案与讨论
- Awaiting more than one future -- as a group
- Question1
- Question2