这个问题是
Threading issues when using HttpClient for asynchronous file downloads的后续问题.
要使用HttpClient异步完成文件传输,需要将HttpCompletionOption.ResponseHeadersRead添加到SendAsync请求.因此,当该调用完成时,您将能够通过添加对EnsureSuccessStatusCode的调用来确定所有对请求和响应头的好处.但是,此时数据可能仍在传输.
如何检测在返回标头之后但在数据传输完成之前发生的错误?这些错误会如何表现出来?
下面是一些示例代码,第109行标记的问题点带有注释:“// *****想要在这里做更多的错误检查**”
using System; using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Threading.Tasks; 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=1&f=2016&g=d&a=0&b=1&c=1901&ignore=.csv"; static void Main(string[] args) { var activeTaskList = new List<Task>(); string outputDirectory = "StockQuotes"; if (!Directory.Exists(outputDirectory)) { Directory.CreateDirectory(outputDirectory); } while (true) { Console.WriteLine("Enter symbol or [ENTER] to exit:"); string symbol = Console.ReadLine(); if (string.IsNullOrEmpty(symbol)) { break; } Task downloadTask = DownloadDataForStockAsync(outputDirectory,symbol); if (TaskIsActive(downloadTask)) { // This is an asynchronous world - lock the list before updating it! lock (activeTaskList) { activeTaskList.Add(downloadTask); } } else { Console.WriteLine("task completed already?!??!?"); } CleanupTasks(activeTaskList); } Console.WriteLine("Cleaning up"); while (CleanupTasks(activeTaskList)) { Task.Delay(1).Wait(); } } private static bool CleanupTasks(List<Task> activeTaskList) { // reverse loop to allow list item deletions // This is an asynchronous world - lock the list before updating it! lock (activeTaskList) { for (int i = activeTaskList.Count - 1; i >= 0; i--) { if (!TaskIsActive(activeTaskList[i])) { activeTaskList.RemoveAt(i); } } return activeTaskList.Count > 0; } } private static bool TaskIsActive(Task task) { return task != null && task.Status != TaskStatus.Canceled && task.Status != TaskStatus.Faulted && task.Status != TaskStatus.RanToCompletion; } static async Task DownloadDataForStockAsync(string outputDirectory,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 request = new HttpRequestMessage(HttpMethod.Post,requestUrl); var response = await client.SendAsync(request,HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); using (var httpStream = await response.Content.ReadAsStreamAsync()) { var timestampedName = FormatTimestampedString(symbol,true); var filePath = Path.Combine(outputDirectory,timestampedName + ".csv"); using (var fileStream = File.Create(filePath)) { await httpStream.CopyToAsync(fileStream); } } // *****WANT TO DO MORE ERROR CHECKING HERE***** } } catch (HttpRequestException ex) { Console.WriteLine("Exception on thread: {0}: {1}\r\n",System.Threading.Thread.CurrentThread.ManagedThreadId,ex.Message,ex.StackTrace); } catch (Exception ex) { Console.WriteLine("Exception on thread: {0}: {1}\r\n",ex.StackTrace); } } static volatile string lastTimestampedString = string.Empty; static volatile string dummy = string.Empty; static string FormatTimestampedString(string message,bool uniquify = false) { // This is an asynchronous world - lock the shared resource before using it! lock (dummy) //lock (lastTimestampedString) { Console.WriteLine("IN - Thread: {0:D2} lastTimestampedString: {1}",lastTimestampedString); string newTimestampedString; while (true) { DateTime lastDateTime = DateTime.Now; newTimestampedString = string.Format( "{1:D4}_{2:D2}_{3:D2}_{4:D2}_{5:D2}_{6:D2}_{7:D3}_{0}",message,lastDateTime.Year,lastDateTime.Month,lastDateTime.Day,lastDateTime.Hour,lastDateTime.Minute,lastDateTime.Second,lastDateTime.Millisecond ); if (!uniquify) { break; } if (newTimestampedString != lastTimestampedString) { break; } //Task.Delay(1).Wait(); }; lastTimestampedString = newTimestampedString; Console.WriteLine("OUT - Thread: {0:D2} lastTimestampedString: {1}",lastTimestampedString); return lastTimestampedString; } } } }
解决方法
我复制并略微清理了相关代码.
var request = new HttpRequestMessage(HttpMethod.Post,requestUrl); var response = await client.SendAsync(request,HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); using (var httpStream = await response.Content.ReadAsStreamAsync()) { var timestampedName = FormatTimestampedString(symbol,true); var filePath = Path.Combine(outputDirectory,timestampedName + ".csv"); using (var fileStream = File.Create(filePath)) { await httpStream.CopyToAsync(fileStream); } }
问题是,如果在读取流并将其复制到文件中时出现问题该怎么办?
所有逻辑错误都已作为HTTP请求和响应周期的一部分得到解决:服务器已收到您的请求,它已确定它是有效的,它已成功响应(响应的标题部分),现在它正在向您发送结果(响应的正文部分).
现在唯一可能发生的错误是服务器崩溃,连接丢失等等.我的理解是这些将显示为HttpRequestException,这意味着您可以编写如下代码:
try { using (var httpStream = await response.Content.ReadAsStreamAsync()) { var timestampedName = FormatTimestampedString(symbol,true); var filePath = Path.Combine(outputDirectory,timestampedName + ".csv"); using (var fileStream = File.Create(filePath)) { await httpStream.CopyToAsync(fileStream); } } } catch (HttpRequestException e) { ... }
The documenation doesn’t say much,unfortunately. The reference source doesn’t either.所以你最好的选择是从这开始,并且可能记录所有不是HttpRequestException的异常,以防在下载响应主体期间可能抛出另一个异常类型.