快速有效地下载多个文件(异步)

我有这么多文件,我必须下载。 所以我尝试使用新的异步function,如下所示。

var streamTasks = urls.Select(async url => (await WebRequest.CreateHttp(url).GetResponseAsync()).GetResponseStream()).ToList(); var streams = await Task.WhenAll(streamTasks); foreach (var stream in streams) { using (var fileStream = new FileStream("blabla", FileMode.Create)) { await stream.CopyToAsync(fileStream); } } 

我担心这段代码会导致大量内存使用,因为如果有1000个文件包含2MB文件,那么这段代码会将1000 * 2MB流加载到内存中?

我可能会遗漏一些东西,或者我完全正确。 如果我没有错过任何东西,那么最好等待每个请求和消费流是最好的方法吗?

这两种选择都可能存在问题。 一次只下载一个不会扩展并且需要花费时间,同时一次下载所有文件可能是一个负载过多(也就是说,在处理它们之前无需等待所有文件下载)。

我更喜欢用可配置的大小限制这种操作。 一种简单的方法是使用AsyncLock (它使用SemaphoreSlim )。 更健壮的方法是使用具有MaxDegreeOfParallelism TPL Dataflow

 var block = new ActionBlock(url => { var stream = (await WebRequest.CreateHttp(url).GetResponseAsync()).GetResponseStream(); using (var fileStream = new FileStream("blabla", FileMode.Create)) { await stream.CopyToAsync(fileStream); } }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 100 }); 

无论您是否使用async您的代码都会将流加载到内存中。 执行async工作通过返回调用方来处理I / O部分,直到ResponseStream返回为止。

您必须选择让asnt关注async ,而是执行有关读取大流输入的程序。

如果我是你,我会考虑如何将工作量分成块。 您可以并行读取ResponseStream并将每个流保存到不同的源(可能是文件)并从内存中释放。

这是我自己的答案,来自Yuval Itzchakov的chunking想法,我提供实施。 请为此实施提供反馈。

 foreach (var chunk in urls.Batch(5)) { var streamTasks = chunk .Select(async url => await WebRequest.CreateHttp(url).GetResponseAsync()) .Select(async response => (await response).GetResponseStream()); var streams = await Task.WhenAll(streamTasks); foreach (var stream in streams) { using (var fileStream = new FileStream("blabla", FileMode.Create)) { await stream.CopyToAsync(fileStream); } } } 

批量是扩展方法,如下所示。

 public static IEnumerable> Batch(this IEnumerable source, int chunksize) { while (source.Any()) { yield return source.Take(chunksize); source = source.Skip(chunksize); } }