HttpClient ReadAsStringAsync 有进度

Ale*_*dro 4 c# asynchronous stream httpclient async-await

有没有办法获取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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Dai*_*Dai 6

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

是和不是。

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

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

但是您仍然可以向方法的使用者报告某些类型的进度,请参阅下面的示例。您应该使用 .NET 惯用IProgress<T>接口来执行此操作,而不是自行设计。

就像这样:

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." );
    }
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 一般来说,任何async方法或返回Task/的方法Task<T>都应该使用Async后缀命名,因此您的方法应该命名为GetStartupAsync,而不是GetStartup
  • 除非您有IHttpClientFactory可用的,否则不应将 a 包装HttpClientusing块中,因为这可能会导致系统资源耗尽,尤其是在服务器应用程序中。
    • (这个原因很复杂,而且可能会根据您的.NET实现而有所不同(例如,我相信Xamarin的HttpClient没有这个问题),但我不会在这里详细介绍)。
    • 因此,您可以安全地忽略任何有关不处置您的HttpClient. 这是关于始终处置您创建或拥有的任何对象的规则的少数例外之一IDisposable
    • 由于HttpClient是线程安全的,因此这是一种static考虑使用缓存的静态实例的方法。
  • 您也不需要包装HttpResponseMessage.Contentusing块中,因为该Content对象由HttpResponseMessage.