如何在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
相关产品推荐
相关产品推荐

