如何在C#中使用TPL实现异步文件下载

3

我有一个Windows控制台客户端,它会休眠几分钟,然后唤醒并从Rest API请求新的更新。该请求生成包含JSON数据的响应,其中涉及一系列对象,每个对象由ID、描述和屏幕截图组成,并作为URL从API发送到客户端。要求控制台应用程序消耗JSON响应,并针对列表中的每个对象查找URL并尝试下载与每个对象关联的相应图像。代码表达如下:

foreach (var jobject in response)
{
    Console.WriteLine(jobject.id);
    Console.WriteLine(jobject.description);
    if (jobject.shotUrl != null)
    {
        WebClient webclient = new WebClient();
        webclient.DownloadFileAsync(new System.Uri(jobject.shotUrl), "F:\\" + jobject.id + ".jpg");
    }
}

有时候可能会有大约500个JSON对象,这意味着需要下载500张照片...这又意味着创建500个Web客户端。我觉得这不是一个好主意。
我的问题是:如果依靠TPL,我能获得更好的性能吗?如何做到?

3
你的问题自相矛盾。你是想要更好的“性能”还是想要更少的“资源使用”? - zaitsman
请注意,每个端点都有一个默认连接限制,在控制台应用程序中,默认为2。这意味着您永远不会无法同时下载超过这两个。请参见:https://msdn.microsoft.com/zh-cn/library/system.net.servicepointmanager.defaultconnectionlimit(v=vs.110).aspx - rene
2
@rene 在这个问题中有一个答案展示了如何增加并发连接 http://stackoverflow.com/questions/26206412/asynchronous-downloading-files-in-c-sharp - Alrehamy
此外,我认为如果您使用 System.Net.HttpClient 而不是 System.Net.WebClient/System.Net.HttpWebRequest,则并发连接限制和类似的 API 不适用。 - yaakov
1个回答

1
使用 HttpClient 进行并发操作要容易得多,您可以在类中声明一个私有的 HttpClient:
System.Net.Http.HttpClient _client = new System.Net.Http.HttpClient();

然后您可以编写一个方法,下载文件并将它们保存到磁盘中,如下所示:

 private async Task DownloadFile(string shortUrl, string destination)
 {
     using (var response = await _client.GetStreamAsync(shortUrl))
     using (var fileStream = File.Create(destination))
     {
         await response.CopyToAsync(fileStream);
         await fileStream.FlushAsync();
     }
 }

然后您可以像这样使用它:
try
{
     await DownloadFile(jobject.shortUrl, "F:\\" + jobject.id + ".jpg");
}
catch (Exception e)
{
     // Do appropriate exception handling
}

如果您想同时下载所有文件,可以使用Task.WhenAll()。

try
{
     var tasks = response.Select(j => DownloadFile(j.shortUrl, "F:\\" + j.id + ".jpg"));
     await Task.WhenAll(tasks);
}
catch (Exception e)
{
     // Do appropriate exception handling
}

1
我对你的代码进行了基准测试,现在它的性能比我的原始工作还要差。 - Alrehamy
说实话,我根本没有运行这段代码,因为我没有足够的上下文(例如response是什么)。但我相信稍微调整一下异步代码(特别是Task.WhenAll部分),将会提供更好的性能。 - Muhammad Azeez
并行下载文件也应该以异步方式完成:async j => await DownloadFile,而不仅仅是 j => DownloadFile,因为在第二种情况下,它是以并发和同步的方式完成的。 - VMAtm
@VMAtm 我认为 await Task.WhenAll() 已经处理了那个问题。 - Muhammad Azeez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接