使用HttpClient进行异步文件下载

我有一个服务,它返回一个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[] { // }; //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!"); } } } } 

  1. “这真的是异步吗?”

是的,主要是。 DownloadDataForStockAsync()方法将在操作完成之前返回await response.Content.ReadAsStreamAsync()语句。

主要的例外是在方法结束时,您调用Stream.CopyTo() 。 这不是异步的,因为它是一个可能很长的操作可能会导致明显的延迟。 但是,在控制台程序中您不会注意到,因为该方法的继续在线程池而不是原始调用线程中执行。

如果您打算将此代码移动到GUI框架(例如Winforms或WPF),则应将语句更改为await httpStream.CopyToAsync(fileStream);

  1. 有没有办法知道内容的长度,即使它是以分块格式发送? 想想进度吧)。

假设服务器在标题中包含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" } 
  1. 我怎样才能最好地监控进度,以便在所有工作完成之前推迟程序退出。

有很多方法。 我将指出,任何合理的方法都需要您更改DownloadDataForStockAsync()方法,以便它返回Task而不是void 。 否则,您无权访问已创建的任务。 不过你应该这样做,所以这不是什么大问题。 🙂

最简单的方法是保留一个列表,列出你开始的所有任务,然后在退出之前等待它们:

 static void Main(string[] args) { List tasks = new List(); 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()返回。