【问题标题】:HttpClient ReadAsStringAsync with progressHttpClient ReadAsStringAsync 与进度
【发布时间】:2020-11-26 21:40:25
【问题描述】:

有没有办法获取ReadAsStringAsync() 方法的进度? 我只是获取网站的 HTML 内容并进行解析。

public static async Task<returnType> GetStartup(string url = "http://")
{
    using (HttpClient client = new HttpClient())
    {
        client.DefaultRequestHeaders.Add("User-Agent",
            "Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko");
        using (HttpResponseMessage response = await client.GetAsync(url))
        {
            using (HttpContent content = response.Content)
            {
                string result = await content.ReadAsStringAsync();
            }
        }
    }
}

【问题讨论】:

  • 字符串有多长?如果响应大小足以保证进度信息(即大小大于几兆字节),那么您可能不应该将其视为String
  • 另外,你到底想要什么样的进度信息?对于小于 ~10KB 的响应(即几个 TCP 数据包/以太网帧),不可能获得一个数字百分比的进度数字,因为它会一次性从 0% 跳到 100%。
  • @Dai 字符串大小在 3MB 到 10MB 之间
  • 如果没有Content-Length 标头,就不可能指示任何类型的百分比进度。
  • 这些都不相关。 Content-Length 是必需的,没有它你就是 SOL。

标签: c# asynchronous async-await stream httpclient


【解决方案1】:

有没有办法获取ReadAsStringAsync() 方法的进度?我只是获取网站的 html 内容并进行解析。

是和不是。

HttpClient 不会从底层网络堆栈公开时间和进度信息,但您可以通过使用 HttpCompletionOption.ResponseHeadersReadContent-Length 标头并读取响应来获取一些信息使用自己的StreamReader(当然是异步的)。

请注意,响应标头中的Content-Length 指的是解压缩前压缩内容的长度,而不是原始内容长度,这会使事情变得复杂,因为可能大多数 今天的网络服务器将提供带有gzip 压缩的HTML(和静态内容)(作为Content-EncodingTransfer-Encoding),因此Content-Length 标头不会告诉您解压缩内容的长度。不幸的是,虽然HttpClient 可以为您自动进行 GZip 解压缩,但它不会告诉您解压缩的内容长度是多少。

但是您可以仍然向您的方法的使用者报告某些类型的进度,请参见下面的示例。您应该使用 .NET 惯用的 IProgress&lt;T&gt; 接口来执行此操作,而不是自己滚动。

像这样:

private static readonly HttpClient _hc = new HttpClient()
{
    DefaultRequestHeaders =
    {
        { "User-Agent", "Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko" }
    }
    // NOTE: Automatic Decompression is not enabled in this HttpClient so that Content-Length can be safely used. But this will drastically slow down content downloads.
};

public static async Task<T> GetStartupAsync( IProgress<String> progress, string url = "http://")
{
    progress.Report( "Now making HTTP request..." );

    using( HttpResponseMessage response = await client.GetAsync( url, HttpCompletionOption.ResponseHeadersRead ) )
    {
        progress.Report( "Received HTTP response. Now reading response content..." );

        Int64? responseLength = response.Content.Headers.ContentLength;
        if( responseLength.HasValue )
        {
            using( Stream responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false) )
            using( StreamReader rdr = new StreamReader( responseStream ) )
            {
                Int64 totalBytesRead = 0;
                StringBuilder sb = new StringBuilder( capacity: responseLength.Value ); // Note that `capacity` is in 16-bit UTF-16 chars, but responseLength is in bytes, though assuming UTF-8 it evens-out.

                Char[] charBuffer = new Char[4096];
                while( true )
                {
                    Int32 read = await rdr.ReadAsync( charBuffer ).ConfigureAwait(false);
                    sb.Append( charBuffer, 0, read );

                    if( read === 0 )
                    {
                        // Reached end.
                        progress.Report( "Finished reading response content." );
                        break;
                    }
                    else
                    {
                        progress.Report( String.Format( CultureInfo.CurrentCulture, "Read {0:N0} / {1:N0} chars (or bytes).", sb.Length, resposneLength.Value );
                    }
                }
            }
        }
        else
        {
            progress.Report( "No Content-Length header in response. Will read response until EOF." );
            
            string result = await content.ReadAsStringAsync();
        }
       
        progress.Report( "Finished reading response content." );
    }

注意事项:

  • 一般而言,任何async 方法或返回Task/Task&lt;T&gt; 的方法都应以Async 后缀命名,因此您的方法应命名为GetStartupAsync,而不是GetStartup
  • 除非您有可用的IHttpClientFactory,否则您不应HttpClient 包装在using 块中,因为这会导致系统资源耗尽,尤其是在服务器应用程序中。
    • (原因很复杂,也可能因你的 .NET 实现而异(例如我相信 Xamarin 的 HttpClient 没有这个问题),但我不会在这里详细说明。
    • 因此,您可以放心地忽略任何关于不处理您的HttpClient 的代码分析警告。这是关于始终处置您创建或拥有的任何 IDisposable 对象的规则的少数例外情况之一。
    • 由于HttpClient 是线程安全的,并且这是static 方法,请考虑使用缓存的静态实例。
  • 您也不需要将HttpResponseMessage.Content 包装在using 块中,因为Content 对象归HttpResponseMessage 所有。

【讨论】:

  • 正如我在上面的评论中所说,Content-Length 是可用的。我想我应该选择 ReadAsStreamAsync?
  • @Alejandro 我已经更新了我的答案以说明Content-Length
  • 你的错字太多了。我清理了它,但你也应该这样做
  • @Alejandro 我的答案中的代码仅用作说明性示例,并不打算复制并粘贴到生产中。 您不应该盲目地从 StackOverflow 或任何其他网站上复制和粘贴代码。
  • 我不是在谈论我,我是在谈论未来的参考。我真的不需要任何 ReadAsStringAsync 说明,我的问题是关于 ReadAsStringAsync 而不是流媒体。我接受了答案,因为我发现除了使用流媒体之外,ReadAsStringAsync 没有解决方案。反正如你所愿
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-16
相关资源
最近更新 更多