使用 HttpClient 进行异步文件下载
Using HttpClient for Asynchronous File Downloads
我有一个服务 returns 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!");
}
}
}
}
- "Is this really asynchronous?"
是的,主要是。 DownloadDataForStockAsync()
方法将在操作完成之前 return,在 await response.Content.ReadAsStreamAsync()
语句处。
主要异常发生在方法的末尾附近,您在此处调用 Stream.CopyTo()
。这不是异步的,因为它可能是一个冗长的操作,可能会导致明显的延迟。但是,在控制台程序中您不会注意到,因为该方法的延续是在线程池中执行的,而不是在原始调用线程中执行的。
如果您打算将此代码移动到 GUI 框架,例如 Winforms 或 WPF,您应该将语句更改为 await httpStream.CopyToAsync(fileStream);
- Is there a way to know the length of the content even though it is being sent in chunked format? Think progress bars).
假设服务器在 headers 中包含 Content-Length
(应该如此),是的。这应该是可以的。
请注意,如果您使用 HttpWebRequest
,响应 object 将有一个 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"
}
- How can I best monitor progress in order to hold off the program exit until all work is complete.
有很多方法。我要指出的是,任何合理的方法都需要您更改 DownloadDataForStockAsync()
方法,使其 return 变为 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
object 的列表,包括那些已经完成的。如果您打算长时间 运行 并处理大量符号,那可能会让人望而却步。在这种情况下,您可能更愿意使用 CountDownEvent
object:
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()
到 return.
我有一个服务 returns 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!");
}
}
}
}
- "Is this really asynchronous?"
是的,主要是。 DownloadDataForStockAsync()
方法将在操作完成之前 return,在 await response.Content.ReadAsStreamAsync()
语句处。
主要异常发生在方法的末尾附近,您在此处调用 Stream.CopyTo()
。这不是异步的,因为它可能是一个冗长的操作,可能会导致明显的延迟。但是,在控制台程序中您不会注意到,因为该方法的延续是在线程池中执行的,而不是在原始调用线程中执行的。
如果您打算将此代码移动到 GUI 框架,例如 Winforms 或 WPF,您应该将语句更改为 await httpStream.CopyToAsync(fileStream);
- Is there a way to know the length of the content even though it is being sent in chunked format? Think progress bars).
假设服务器在 headers 中包含 Content-Length
(应该如此),是的。这应该是可以的。
请注意,如果您使用 HttpWebRequest
,响应 object 将有一个 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"
}
- How can I best monitor progress in order to hold off the program exit until all work is complete.
有很多方法。我要指出的是,任何合理的方法都需要您更改 DownloadDataForStockAsync()
方法,使其 return 变为 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
object 的列表,包括那些已经完成的。如果您打算长时间 运行 并处理大量符号,那可能会让人望而却步。在这种情况下,您可能更愿意使用 CountDownEvent
object:
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()
到 return.