zoukankan      html  css  js  c++  java
  • 实现在 .net 中使用 HttpClient 下载文件时显示进度

    在 .net framework 中,要实现下载文件并显示进度的话,最简单的做法是使用 WebClient 类。订阅 DownloadProgressChanged 事件就行了。

    但是很可惜,WebClient 并不包含在 .net standard 当中。在 .net standard 中,要进行 http 网络请求,我们用得更多的是 HttpClient。另外还要注意的是,UWP 中也有一个 HttpClient,虽然用法差不多,但是命名空间是不一样的,而且 UWP 的是可以支持获取下载进度的,这里就不再细说。

    如果要下载文件,我们会使用到 HttpClient 的 GetByteArrayAsync 这个方法。要实现下载进度,那要怎么办呢?俗话说,不行就包一层。这里我们写个扩展方法,定义如下:

    public static class HttpClientExtensions
    {
        public static Task<byte[]> GetByteArrayAsync(this HttpClient client, Uri requestUri, IProgress<HttpDownloadProgress> progress, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }
    }

    其中 HttpDownloadProgress 是我自己定义的结构体(不使用类的原因我下面再说),代码如下:

    public struct HttpDownloadProgress
    {
        public ulong BytesReceived { get; set; }
    
        public ulong? TotalBytesToReceive { get; set; }
    }

    BytesReceived 代表已经下载的字节数,TotalBytesToReceive 代表需要下载的字节数,因为 http 的响应头不一定会返回长度(content-length),所以这里设置为可空。

    由于我们需要从 http 响应头获取到 content-length,而 HttpClient 自身的 GetByteArrayAsync 并没有办法实现,我们需要转向使用 GetAsync 这个方法。GetAsync 这个方法有一个重载 https://docs.microsoft.com/zh-cn/dotnet/api/system.net.http.httpclient.getasync#System_Net_Http_HttpClient_GetAsync_System_Uri_System_Net_Http_HttpCompletionOption_System_Threading_CancellationToken_ 它的第二个参数是一个枚举,代表是什么时候可以得到 response。按照需求,我们这里应该使用 HttpCompletionOption.ResponseHeadersRead 这个。

    另外 HttpClient 的源码也可以在 Github 上看得到。https://github.com/dotnet/corefx/blob/d69d441dfb0710c2a34155c7c4745db357b14c96/src/System.Net.Http/src/System/Net/Http/HttpClient.cs 我们可以参考一下 GetByteArrayAsync 的实现。

    经过思考,可以写出下面的代码:

    public static class HttpClientExtensions
    {
        private const int BufferSize = 8192;
    
        public static async Task<byte[]> GetByteArrayAsync(this HttpClient client, Uri requestUri, IProgress<HttpDownloadProgress> progress, CancellationToken cancellationToken)
        {
            if (client == null)
            {
                throw new ArgumentNullException(nameof(client));
            }
    
            using (var responseMessage = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false))
            {
                responseMessage.EnsureSuccessStatusCode();
    
                var content = responseMessage.Content;
                if (content == null)
                {
                    return Array.Empty<byte>();
                }
    
                var headers = content.Headers;
                var contentLength = headers.ContentLength;
                using (var responseStream = await content.ReadAsStreamAsync().ConfigureAwait(false))
                {
                    var buffer = new byte[BufferSize];
                    int bytesRead;
                    var bytes = new List<byte>();
    
                    var downloadProgress = new HttpDownloadProgress();
                    if (contentLength.HasValue)
                    {
                        downloadProgress.TotalBytesToReceive = (ulong)contentLength.Value;
                    }
                    progress?.Report(downloadProgress);
    
                    while ((bytesRead = await responseStream.ReadAsync(buffer, 0, BufferSize, cancellationToken).ConfigureAwait(false)) > 0)
                    {
                        bytes.AddRange(buffer.Take(bytesRead));
    
                        downloadProgress.BytesReceived += (ulong)bytesRead;
                        progress?.Report(downloadProgress);
                    }
    
                    return bytes.ToArray();
                }
            }
        }
    }

    这里我将缓冲区设置为 8192 字节(8 KB),相当于每读取 8 KB 就汇报一次下载进度,当然各位看官也可以把这个值调小,这样效果会更好,但相对的性能就差一些。同时也因为这里 Report 的频率是比较高的,因此 HttpDownloadProgress 不适合用 class(否则 GC 会压力相当大)。

    下面我自己的 Demo 的效果图:

    iiiw

  • 相关阅读:
    Mybatis之批量更新操作
    Spring Quartz *.QRTZ_LOCKS' doesn't exist
    分析NTFS文件系统得到特定文件的内容
    设计模式笔记——设计模式原则总结
    android自己定义ViewPager之——3D效果应用
    Android混淆代码
    百度地图 Android SDK
    NYOJ17,单调递增最长子序列
    令人纠结的两行代码
    XCode中在提示窗体中对已弃用的API接口画上红线
  • 原文地址:https://www.cnblogs.com/h82258652/p/10950580.html
Copyright © 2011-2022 走看看