进度条与HttpClient

我有一个文件下载function:

HttpClientHandler aHandler = new HttpClientHandler(); aHandler.ClientCertificateOptions = ClientCertificateOption.Automatic; HttpClient aClient = new HttpClient(aHandler); aClient.DefaultRequestHeaders.ExpectContinue = false; HttpResponseMessage response = await aClient.GetAsync(url); InMemoryRandomAccessStream randomAccessStream = new InMemoryRandomAccessStream(); // To save downloaded image to local storage var imageFile = await ApplicationData.Current.LocalFolder.CreateFileAsync( filename, CreationCollisionOption.ReplaceExisting); var fs = await imageFile.OpenAsync(FileAccessMode.ReadWrite); DataWriter writer = new DataWriter(fs.GetOutputStreamAt(0)); writer.WriteBytes(await response.Content.ReadAsByteArrayAsync()); await writer.StoreAsync(); //current.image.SetSource(randomAccessStream); writer.DetachStream(); await fs.FlushAsync(); 

我怎样才能实现进度条function? 也许我可以得到目前写入的作家字节? 或者其他的东西?

PS我无法使用DownloadOperation(后台传输),因为来自服务器请求证书的数据 – 而这个function在DownloadOperations中不存在。

最好的方法是使用Windows.Web.Http.HttpClient而不是System.Net.Http.HttpClient 。 第一个支持进展。

但是,如果由于某种原因你想坚持System.Net,你将需要实现自己的进步。

删除DataWriter ,删除InMemoryRandomAccessStream 并将HttpCompletionOption.ResponseHeadersRead添加到GetAsync调用,以便在收到标题时立即返回,而不是在收到整个响应时返回。 即:

 // Your original code. HttpClientHandler aHandler = new HttpClientHandler(); aHandler.ClientCertificateOptions = ClientCertificateOption.Automatic; HttpClient aClient = new HttpClient(aHandler); aClient.DefaultRequestHeaders.ExpectContinue = false; HttpResponseMessage response = await aClient.GetAsync( url, HttpCompletionOption.ResponseHeadersRead); // Important! ResponseHeadersRead. // To save downloaded image to local storage var imageFile = await ApplicationData.Current.LocalFolder.CreateFileAsync( filename, CreationCollisionOption.ReplaceExisting); var fs = await imageFile.OpenAsync(FileAccessMode.ReadWrite); // New code. Stream stream = await response.Content.ReadAsStreamAsync(); IInputStream inputStream = stream.AsInputStream(); ulong totalBytesRead = 0; while (true) { // Read from the web. IBuffer buffer = new Windows.Storage.Streams.Buffer(1024); buffer = await inputStream.ReadAsync( buffer, buffer.Capacity, InputStreamOptions.None); if (buffer.Length == 0) { // There is nothing else to read. break; } // Report progress. totalBytesRead += buffer.Length; System.Diagnostics.Debug.WriteLine("Bytes read: {0}", totalBytesRead); // Write to file. await fs.WriteAsync(buffer); } inputStream.Dispose(); fs.Dispose(); 

这是一个自包含的类,它将执行下载,并根据此答案的 TheBlueSky代码和此GitHub评论中的 eriksendc报告进度百分比。

 public class HttpClientDownloadWithProgress : IDisposable { private readonly string _downloadUrl; private readonly string _destinationFilePath; private HttpClient _httpClient; public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage); public event ProgressChangedHandler ProgressChanged; public HttpClientDownloadWithProgress(string downloadUrl, string destinationFilePath) { _downloadUrl = downloadUrl; _destinationFilePath = destinationFilePath; } public async Task StartDownload() { _httpClient = new HttpClient { Timeout = TimeSpan.FromDays(1) }; using (var response = await _httpClient.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead)) await DownloadFileFromHttpResponseMessage(response); } private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response) { response.EnsureSuccessStatusCode(); var totalBytes = response.Content.Headers.ContentLength; using (var contentStream = await response.Content.ReadAsStreamAsync()) await ProcessContentStream(totalBytes, contentStream); } private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream) { var totalBytesRead = 0L; var readCount = 0L; var buffer = new byte[8192]; var isMoreToRead = true; using (var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true)) { do { var bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length); if (bytesRead == 0) { isMoreToRead = false; TriggerProgressChanged(totalDownloadSize, totalBytesRead); continue; } await fileStream.WriteAsync(buffer, 0, bytesRead); totalBytesRead += bytesRead; readCount += 1; if (readCount % 100 == 0) TriggerProgressChanged(totalDownloadSize, totalBytesRead); } while (isMoreToRead); } } private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead) { if (ProgressChanged == null) return; double? progressPercentage = null; if (totalDownloadSize.HasValue) progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2); ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage); } public void Dispose() { _httpClient?.Dispose(); } } 

用法:

 var downloadFileUrl = "http://example.com/file.zip"; var destinationFilePath = Path.GetFullPath("file.zip"); using (var client = new HttpClientDownloadWithProgress(downloadFileUrl, destinationFilePath)) { client.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) => { Console.WriteLine($"{progressPercentage}% ({totalBytesDownloaded}/{totalFileSize})"); }; await client.StartDownload(); } 

结果:

 7.81% (26722304/342028776) 8.05% (27535016/342028776) 8.28% (28307984/342028776) 8.5% (29086548/342028776) 8.74% (29898692/342028776) 8.98% (30704184/342028776) 9.22% (31522816/342028776) 

下面的代码显示了一个必须对HttpClient api进行下载的最简单的例子。

 HttpClient client = //... // Must use ResponseHeadersRead to avoid buffering of the content using (var response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead)){ // You must use as stream to have control over buffering and number of bytes read/received using (var stream = await response.Content.ReadAsStreamAsync()) { // Read/process bytes from stream as appropriate // Calculated by you based on how many bytes you have read. Likely incremented within a loop. long bytesRecieved = //... long? totalBytes = response.Content.Headers.ContentLength; double? percentComplete = (double)bytesRecieved / totalBytes; // Do what you want with `percentComplete` } } 

上面并没有告诉你如何处理stream,如何报告stream程,或者尝试直接解决原始问题中的代码。 然而,对于那些希望申请进入代码的读者来说,这个答案可能更容易被读取。

嗯,你可以有另一个线程检查正在写入的stream的当前大小(你也可以将预期的文件大小传递给它),然后相应地更新进度条。

自.Net 4.5以来,您可以使用IProgress<T>界面处理asynchronous进度报告。 然后你可以写一个扩展方法来使用HttpClient下载文件。 请注意,此扩展依赖于另一个处理asynchronousstream复制与进度报告的扩展。

 public static class HttpClientExtensions { public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress<float> progress = null, CancellationToken cancellationToken = default) { using (var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead)) { var contentLength = response.Content.Headers.ContentLength; using (var download = await response.Content.ReadAsStreamAsync()) { if (progress == null || !contentLength.HasValue) { await download.CopyToAsync(destination); return; } // Convert absolute progress (bytes downloaded) into relative progress (0% - 100%) var relativeProgress = new Progress<long>(totalBytes => progress.Report((float)totalBytes / contentLength.Value)); // Use extension method to report progress while downloading await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken); progress.Report(1); } } } } 

通过真实进度报告的stream式扩展:

 public static class StreamExtensions { public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long> progress = null, CancellationToken cancellationToken = default) { if (source == null) throw new ArgumentNullException(nameof(source)); if (!source.CanRead) throw new ArgumentException("Has to be readable", nameof(source)); if (destination == null) throw new ArgumentNullException(nameof(destination)); if (!destination.CanWrite) throw new ArgumentException("Has to be writable", nameof(destination)); if (bufferSize < 0) throw new ArgumentOutOfRangeException(nameof(bufferSize)); var buffer = new byte[bufferSize]; long totalBytesRead = 0; int bytesRead; while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) { await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); totalBytesRead += bytesRead; progress?.Report(totalBytesRead); } } } 

然后使用下面的代码下载一个文件:

 using (var client = new HttpClient()) { client.Timeout = TimeSpan.FromMinutes(5); using (var file = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) { await client.DownloadAsync(DownloadUrl, file, progress, cancellationToken); } }