【问题标题】:Flutter: Future not waitingFlutter:未来不等待
【发布时间】:2020-06-08 02:47:46
【问题描述】:

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

我的代码:

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

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

【问题讨论】:

    标签: flutter future


    【解决方案1】:

    Crazy Lazy Cat's answer 很棒而且很紧凑。但是,如果您需要进一步的操作并且不能使用这样的地图,那么您必须寻求类似于您的代码的解决方案。尽管这些方法有一个警告,即不应从并发/异步执行的期货中修改列表/可迭代对象。转到我的答案的末尾以进一步了解这一点。

    无论如何,根据解决方案,在这种情况下,您有两个选择:

    选项 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。

    按顺序遍历列表

    在这种情况下,每次迭代都会等待,直到执行下一次。性能不如选项 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;
    }
    

    说明

    访问的集合(数组,映射,...)的添加/删除必须在futures之外完成,否则在迭代期间同时修改一个iterable会引发运行时错误。

    参考文献

    解决方案和讨论

    【讨论】:

      【解决方案2】:

      使用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 循环好多了(只能一首接一首歌曲)。

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

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

      【讨论】:

      • @Eugene 并行处理请求所获得的时间是无限有益的
      • 请记住,使用这种方法可能会导致ConcurrentModificationError——尽管这种方法提供了不错的并发性/性能,但在使用这种方法时,您不能更改集合(List、Map、Set ,...) 通过在其中添加或删除项目。这将使迭代器无效并导致运行时异常ConcurrentModificationError。即对访问的集合(数组,映射,...)的添加/删除必须在期货之外完成
      • @om-ha songs.map 产生Futures 的列表,然后由Future.wait 处理以获得结果列表作为输出。我认为这里不会出现此ConcurrentModificationError 错误。
      • @Eugene 运行此代码Dartpad。你可能明白我所说的并行是什么意思。
      • @aligator 他们这样做了,map 正在返回尚未执行的期货列表。 Future.wait 并行执行它们。
      【解决方案3】:

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

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

      【讨论】:

      • 这可以解决问题并返回您希望的有效结果,但它不够并行/并发 - 它是一种 sequential 方法而不是 并发方法。我将在接下来的回答中说明这一点。
      • 添加了一个answer 解释和说明我们解决这个问题的选项。
      猜你喜欢
      • 1970-01-01
      • 2020-08-11
      • 1970-01-01
      • 2019-10-08
      • 2022-06-11
      • 2021-03-17
      • 2017-06-29
      • 2021-03-26
      • 2015-01-07
      相关资源
      最近更新 更多