Индикатор выполнения с HttpClient

У меня есть функция загрузчика файлов:

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(); 

Вот самостоятельный class, который сделает загрузку, и отчитайте процент прогресса, основанный на коде из TheBlueSky на этом SO-ответе , и eriksendc в этом комментарии GitHub .

 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) 

Начиная с .Net 4.5: Использовать IProgress

Начиная с .Net 4.5 вы можете обрабатывать отчеты о IProgress асинхронов с помощью IProgress . Вы можете написать метод расширения для загрузки файлов с помощью HttpClient который можно вызвать так, как это происходит, когда progress – это реализация IProgress для вашей пороговой панели или других элементов интерфейса:

 // 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); } } 

Реализация

Код этого метода расширения выглядит следующим образом. Обратите внимание, что это расширение зависит от другого расширения для обработки асинхронного streamа с отчетами о ходе выполнения.

 public static class HttpClientExtensions { public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress 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(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 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); } } } 

Следующий код показывает минимальный пример того, что должно быть сделано против api HttpClient для получения загрузки.

 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, как сообщать о процессе или пытаться обеспечить прямое решение кода в исходном вопросе. Тем не менее, этот ответ может быть более доступным для будущих читателей, которые хотят применить прогресс в своем коде.

Hm, вы могли бы еще один stream проверить текущий размер записываемого streamа (вы также должны передать ему ожидаемый размер файла), а затем обновить индикатор выполнения.

то же, что и решение @ René Sackers выше, но добавило возможность отменить загрузку

 class HttpClientDownloadWithProgress : IDisposable { private readonly string _downloadUrl; private readonly string _destinationFilePath; private readonly CancellationToken? _cancellationToken; private HttpClient _httpClient; public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage); public event ProgressChangedHandler ProgressChanged; public HttpClientDownloadWithProgress(string downloadUrl, string destinationFilePath, CancellationToken? cancellationToken = null) { _downloadUrl = downloadUrl; _destinationFilePath = destinationFilePath; _cancellationToken = cancellationToken; } 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 { int bytesRead; if (_cancellationToken.HasValue) { bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, _cancellationToken.Value); } else { bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length); } if (bytesRead == 0) { isMoreToRead = false; continue; } await fileStream.WriteAsync(buffer, 0, bytesRead); totalBytesRead += bytesRead; readCount += 1; if (readCount % 10 == 0) TriggerProgressChanged(totalDownloadSize, totalBytesRead); } while (isMoreToRead); } //the last progress trigger should occur after the file handle has been released or you may get file locked error TriggerProgressChanged(totalDownloadSize, totalBytesRead); } 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(); } } 
  • Как мне ZIP-файл на C #, без использования сторонних API-интерфейсов?
  • Загрузите файл с помощью JSF?
  • Внедрение простого сервлета загрузки файлов
  • Скачать файл с помощью libcurl в C / C ++
  • Загрузка файлов с помощью HTTPWebrequest (multipart / form-data)
  • Как загрузить файлы на серверной папке с помощью jsp
  • Visual C ++ 2008 Express Загрузить ссылку Dead?
  • Как изменить таймаут на объекте .NET WebClient
  • ASP.NET Как передать файл пользователю
  • Использование подстановочных знаков в запросе wget или curl
  • Как отправить байт в формате pdf в браузер в java-приложении?
  • Давайте будем гением компьютера.