Флаттер: будущее не ждет - PullRequest
       15

Флаттер: будущее не ждет

0 голосов
/ 24 февраля 2020

Мой метод будущего не ждет, пока мой список будет создан, прежде чем он вернет мой список. Он возвращает пустой список и затем правильно создает мой список.

Мой код:

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']

Как это исправить? Спасибо!

Ответы [ 3 ]

3 голосов
/ 24 февраля 2020

Используйте 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)));
}

Намного лучше, чем для l oop (который получает песню только один за другим).

Этот код может помочь лучше понять: DartPad .

( Примечание: При нажатии на ссылку, дартпад автоматически начнет выполнение кода. Если Вы нажимаете кнопку запуска, вы можете увидеть нежелательное поведение, поэтому не нажимайте кнопку запуска во время выполнения кода)

0 голосов
/ 24 февраля 2020

Ответ Crazy Lazy Cat великолепен и компактен. Но если вам нужны дальнейшие операции и вы не можете использовать такую ​​карту, вам нужно go для решения, похожего на ваш код. Хотя у этих методов есть предостережение, что списки / итерации не должны изменяться из WITHIN во фьючерсы, которые выполняются одновременно / асинхронно. Go до конца моего ответа, чтобы понять это больше.

В любом случае, согласно решению, в этом случае у вас есть две опции:

Опция A.

Перебор списка в пакетном режиме с использованием Future.wait

В этом случае все итерации будут выполняться параллельно (теоретически). Это обеспечивает максимальную производительность / параллелизм.

Все сводится к хранению фьючерсов перед асинхронным их исполнением с использованием 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.

Итерация по list последовательно

В этом случае каждая итерация ожидается, пока не будет выполнена следующая. Производительность не так хороша, как у варианта A, поскольку ожидается, что каждая итерация / вызов останавливает поток управления, следующая итерация / вызов не начнется, пока не закончится предыдущая. Этот метод несколько безопаснее, чем предыдущий, в отношении 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;
}

Объяснение

Добавление / удаление в доступную коллекцию (массив, карту, ...) должно выполняться вне фьючерсов, в противном случае будет выдана ошибка времени выполнения для одновременной модификации итерируемой во время итерации.

Ссылки

Решения и обсуждения

0 голосов
/ 24 февраля 2020

почему бы вам не поменять на forEach только на l oop?

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

}
...