在 .net framework 中,要實現下載文件並顯示進度的話,最簡單的作法是使用 WebClient 類。訂閱 DownloadProgressChanged 事件就好了。git
可是很惋惜,WebClient 並不包含在 .net standard 當中。在 .net standard 中,要進行 http 網絡請求,咱們用得更多的是 HttpClient。另外還要注意的是,UWP 中也有一個 HttpClient,雖然用法差很少,可是命名空間是不同的,並且 UWP 的是能夠支持獲取下載進度的,這裏就再也不細說。github
若是要下載文件,咱們會使用到 HttpClient 的 GetByteArrayAsync 這個方法。要實現下載進度,那要怎麼辦呢?俗話說,不行就包一層。這裏咱們寫個擴展方法,定義以下:web
public static class HttpClientExtensions { public static Task<byte[]> GetByteArrayAsync(this HttpClient client, Uri requestUri, IProgress<HttpDownloadProgress> progress, CancellationToken cancellationToken) { throw new NotImplementedException(); } }
其中 HttpDownloadProgress 是我本身定義的結構體(不使用類的緣由我下面再說),代碼以下:c#
public struct HttpDownloadProgress { public ulong BytesReceived { get; set; } public ulong? TotalBytesToReceive { get; set; } }
BytesReceived 表明已經下載的字節數,TotalBytesToReceive 表明須要下載的字節數,由於 http 的響應頭不必定會返回長度(content-length),因此這裏設置爲可空。api
因爲咱們須要從 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 的實現。async
通過思考,能夠寫出下面的代碼:性能
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 會壓力至關大)。this
下面我本身的 Demo 的效果圖:spa