我有一个文件下载功能:
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();
我怎样才能实现进度条功能?也许到目前为止我可以写出写入的字节数?或者其他的东西?
PS我无法使用DownloadOperation(后台传输),因为来自服务器的数据请求证书 - 并且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();
IProgress<T>
从.Net 4.5开始,您可以使用该IProgress<T>
界面处理异步进度报告.你可以写在下载使用这些文件的扩展方法HttpClient
,可以这样调用这里progress
是执行IProgress<float>
您porgress酒吧或其他UI的东西:
// Seting up the http client used to download the data using (var client = new HttpClient()) { client.Timeout = TimeSpan.FromMinutes(5); // Create a file stream to store the downloaded data. // This really can be any type of writeable stream. using (var file = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) { // Use the custom extension method below to download the data. // The passed progress-instance will receive the download status updates. await client.DownloadAsync(DownloadUrl, file, progress, cancellationToken); } }
此扩展方法的代码如下所示.请注意,此扩展依赖于另一个扩展来处理具有进度报告的异步流复制.
public static class HttpClientExtensions { public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress<float> progress = null, CancellationToken cancellationToken = default) { // Get the http headers first to examine the content length using (var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead)) { var contentLength = response.Content.Headers.ContentLength; using (var download = await response.Content.ReadAsStreamAsync()) { // Ignore progress reporting when no progress reporter was // passed or when the content length is unknown 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); } } } }
通过流扩展实现真正的进度报告:
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); } } }
这是一个自包含的类,它将进行下载,并根据TheSlueSky在此SO答案中的代码报告进度百分比,并在此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)