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

如何在Dart中顺序执行异步函数:解决多文件并发下载问题

如何实现Dart中顺序执行文件下载任务

你遇到的核心问题其实是你的download函数没有正确等待整个下载流程完成,导致Future.forEach虽然设计为顺序调度任务,但每个process调用都瞬间结束,最终所有下载任务同时启动,给服务器带来压力。咱们一步步来解决这个问题:

第一步:修复download函数,让它真正等待下载完成

你当前的代码里,response.listen(...)只是注册了数据流的监听,但没有等待整个流处理完成——onDone只是一个回调,不会让download函数暂停等待。要让download成为一个真正能被await的异步函数,需要把流处理改成可等待的方式,这里有两种简洁的实现方式:

方式A:使用Stream.pipe()(推荐,代码更简洁)

pipe()方法会自动把响应的数据流写入文件,流结束时自动关闭文件,并且返回一个Future,等待它完成就意味着整个下载+写入流程结束了:

Future<void> download(String url, String path) async {
  var len = await getFileSizeIfExists(path); // 获取已下载的字节数,用于断点续传
  final file = File(path);
  
  final httpClient = HttpClient();
  final request = await httpClient.getUrl(Uri.parse(url));
  
  // 断点续传:告诉服务器从指定位置开始下载
  if (len > 0) {
    request.headers.add('Range', 'bytes=$len-');
    print('Resuming from the $len byte');
  }

  final response = await request.close();
  // 选择文件写入模式:断点续传用append,从头下载用write
  final fileSink = len > 0 ? file.openWrite(mode: FileMode.append) : file.openWrite();
  
  // 等待整个数据流写入完成
  await response.pipe(fileSink);
  
  httpClient.close();
  print('Download finished: $path');
}

方式B:手动监听流并等待完成

如果你需要自己处理进度更新,可以用asFuture()将StreamSubscription转为Future,等待流结束:

Future<void> download(String url, String path) async {
  var len = await getFileSizeIfExists(path);
  final file = File(path);
  final fileSink = len > 0 ? file.openWrite(mode: FileMode.append) : file.openWrite();
  
  final httpClient = HttpClient();
  final request = await httpClient.getUrl(Uri.parse(url));
  if (len > 0) {
    request.headers.add('Range', 'bytes=$len-');
    print('Resuming from the $len byte');
  }

  final response = await request.close();
  
  // 监听数据流,更新进度,同时等待流结束
  await response.listen((data) {
    fileSink.add(data);
    // 这里加入你的进度更新逻辑,比如计算已下载字节数、更新进度条
    updateProgress(len += data.length);
  }).asFuture();
  
  await fileSink.close();
  httpClient.close();
  print('Download finished: $path');
}

第二步:实现顺序下载

当download函数被修复后,你有两种简单的方式实现顺序下载:

方式1:普通for循环(最直观,推荐)

这种方式逻辑清晰,绝对保证顺序执行,每一次循环都会等待当前下载完成后才会启动下一个:

void main() async {
  final urls = ['url1', 'url2', 'url3', ...];
  
  for (final url in urls) {
    // 可以根据url生成对应的本地文件路径
    final fileName = url.split('/').last;
    await download(url, './downloads/$fileName');
  }
}

方式2:修复后使用Future.forEach

当download正确等待完成后,Future.forEach就会按顺序执行每个任务——它会等待前一个Future完成后,才会调度下一个任务的执行:

void main() async {
  final urls = ['url1', 'url2', 'url3', ...];
  
  await Future.forEach(urls, (String url) async {
    final fileName = url.split('/').last;
    await download(url, './downloads/$fileName');
  });
}

额外注意点

  • 断点续传时,一定要确认服务器支持Range请求(响应状态码为206 Partial Content),如果服务器不支持,你可能需要重新从头下载文件。
  • 记得关闭HttpClient和文件流,避免资源泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:09:44