使用 HttpClient 进行异步文件下载
Posted
技术标签:
【中文标题】使用 HttpClient 进行异步文件下载【英文标题】:Using HttpClient for Asynchronous File Downloads 【发布时间】:2015-01-10 01:40:58 【问题描述】:我有一项服务可以将 csv 文件返回给 POST 请求。我想使用异步技术下载所述文件。虽然我可以获得该文件,但我的代码有几个突出的问题和疑问:
1) 这真的是异步的吗?
2) 有没有办法知道内容的长度,即使它是以分块格式发送的?想想进度条)。
3) 我怎样才能最好地监控进度,以便在所有工作完成之前推迟程序退出。
using System;
using System.IO;
using System.Net.Http;
namespace TestHttpClient2
class Program
/*
* Use Yahoo portal to access quotes for stocks - perform asynchronous operations.
*/
static string baseUrl = "http://real-chart.finance.yahoo.com/";
static string requestUrlFormat = "/table.csv?s=0&d=0&e=9&f=2015&g=d&a=4&b=5&c=2000&ignore=.csv";
static void Main(string[] args)
while (true)
Console.Write("Enter a symbol to research or [ENTER] to exit: ");
string symbol = Console.ReadLine();
if (string.IsNullOrEmpty(symbol))
break;
DownloadDataForStockAsync(symbol);
static async void DownloadDataForStockAsync(string symbol)
try
using (var client = new HttpClient())
client.BaseAddress = new Uri(baseUrl);
client.Timeout = TimeSpan.FromMinutes(5);
string requestUrl = string.Format(requestUrlFormat, symbol);
//var content = new KeyValuePair<string, string>[]
// ;
//var formUrlEncodedContent = new FormUrlEncodedContent(content);
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
var sendTask = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
var response = sendTask.Result.EnsureSuccessStatusCode();
var httpStream = await response.Content.ReadAsStreamAsync();
string OutputDirectory = "StockQuotes";
if (!Directory.Exists(OutputDirectory))
Directory.CreateDirectory(OutputDirectory);
DateTime currentDateTime = DateTime.Now;
var filePath = Path.Combine(OutputDirectory, string.Format("1:D4_2:D2_3:D2_4:D2_5:D2_6:D2_7:D3_0.csv",
symbol,
currentDateTime.Year, currentDateTime.Month, currentDateTime.Day,
currentDateTime.Hour, currentDateTime.Minute, currentDateTime.Second, currentDateTime.Millisecond
));
using (var fileStream = File.Create(filePath))
using (var reader = new StreamReader(httpStream))
httpStream.CopyTo(fileStream);
fileStream.Flush();
catch (Exception ex)
Console.WriteLine("Error, try again!");
【问题讨论】:
根据您的异步问题查看msdn.microsoft.com/en-us/library/hh191443.aspx 【参考方案1】:“这真的是异步的吗?”
是的,主要是。 DownloadDataForStockAsync()
方法将在操作完成之前返回,在 await response.Content.ReadAsStreamAsync()
语句处。
主要的例外是在方法的末尾附近,您调用Stream.CopyTo()
。这不是异步的,因为它是一个潜在的冗长操作,可能会导致明显的延迟。但是,在控制台程序中您不会注意到,因为方法的延续是在线程池中执行的,而不是在原始调用线程中执行的。
如果您打算将此代码移至 GUI 框架,例如 Winforms 或 WPF,则应将语句更改为 await httpStream.CopyToAsync(fileStream);
有没有办法知道内容的长度,即使它是以分块格式发送的?想想进度条)。
假设服务器在标题中包含Content-Length
(并且应该),是的。这应该是可能的。
请注意,如果您使用的是HttpWebRequest
,则响应对象将有一个ContentLength
属性直接为您提供此值。你在这里使用HttpRequestMessage
,我不太熟悉。但据我所知,您应该能够像这样访问Content-Length
值:
long? contentLength = response.Content.Headers.ContentLength;
if (contentLength != null)
// use value to initialize "determinate" progress indication
else
// no content-length provided; will need to display progress as "indeterminate"
我怎样才能最好地监控进度,以便在所有工作完成之前推迟程序退出。
有很多方法。我要指出,任何合理的方法都需要您更改DownloadDataForStockAsync()
方法,使其返回Task
而不是void
。否则,您无权访问已创建的任务。不过,无论如何你都应该这样做,所以这没什么大不了的。 :)
最简单的方法是保留一份您启动的所有任务的列表,然后在退出前等待它们:
static void Main(string[] args)
List<Task> tasks = new List<Task>();
while (true)
Console.Write("Enter a symbol to research or [ENTER] to exit: ");
string symbol = Console.ReadLine();
if (string.IsNullOrEmpty(symbol))
break;
tasks.Add(DownloadDataForStockAsync(symbol));
Task.WaitAll(tasks);
当然,这需要您明确维护每个Task
对象的列表,包括那些已经完成的对象。如果您打算让它运行很长时间并处理大量符号,那可能会令人望而却步。在这种情况下,您可能更喜欢使用 CountDownEvent
对象:
static void Main(string[] args)
CountDownEvent countDown = new CountDownEvent();
while (true)
Console.Write("Enter a symbol to research or [ENTER] to exit: ");
string symbol = Console.ReadLine();
if (string.IsNullOrEmpty(symbol))
break;
countDown.AddCount();
DownloadDataForStockAsync(symbol).ContinueWith(task => countdown.Signal()) ;
countDown.Wait();
这只是为您创建的每个任务增加CountDownEvent
计数器,并为每个任务附加一个延续以减少计数器。当计数器达到零时,事件被设置,允许对Wait()
的调用返回。
【讨论】:
以上是关于使用 HttpClient 进行异步文件下载的主要内容,如果未能解决你的问题,请参考以下文章