c# - 使用 HttpClient 进行异步文件下载时的错误检查

标签 c# .net asynchronous async-await dotnet-httpclient

此问题是 Threading issues when using HttpClient for asynchronous file downloads 的后续问题.

要使用 HttpClient 异步完成文件传输,您需要将 HttpCompletionOption.ResponseHeadersRead 添加到 SendAsync 请求。因此,当该调用完成时,您将能够通过添加对 EnsureSuccessStatusCode 的调用来确定请求和响应 header 是否一切正常。但是,此时数据可能仍在传输中。

如何检测在返回 header 之后但在数据传输完成之前发生的错误?上述错误将如何表现出来?

下面是一些示例代码,在第 109 行标记了问题的要点)并附有注释:“//*****WANT TO DO MORE ERROR CHECKING HERE**”

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",
          System.Threading.Thread.CurrentThread.ManagedThreadId,
          ex.Message,
          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}",
            System.Threading.Thread.CurrentThread.ManagedThreadId,
            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}",
            System.Threading.Thread.CurrentThread.ManagedThreadId,
            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 请求和响应周期的一部分得到解决:服务器已收到您的请求,它已确定它是有效的,它已成功响应( header 部分响应),它现在向您发送结果(响应的正文部分)。

现在唯一可能发生的错误是服务器崩溃、连接丢失等。我的理解是这些将表现为 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 的异常,以防在下载响应正文期间可能抛出另一种异常类型。

关于c# - 使用 HttpClient 进行异步文件下载时的错误检查,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28075202/

相关文章:

c# - WPF 用户控件子级不更新 c#

c# - Linq lambda 表达式至少得到一个重复项和非重复项

c# - 是否可以在 PostBack C# PCI Compliance 后显示信用卡号

新项目的 Java 或单声道

c# - 无法在 Visual Studio 中放置行号

c# - .NET前端、MYSQL后端的打印解决方案

javascript - 如何使 console.log() 在 Phantomjs 中同步运行?

javascript - 如何从异步调用返回响应?

C# List<T> 包含测试

c# - 为什么在 C# 中没有引用计数 + 垃圾收集?