【问题标题】:Progress bar with HttpClient带有 HttpClient 的进度条
【发布时间】:2013-12-18 15:09:15
【问题描述】:

我有一个文件下载功能:

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

如何实现进度条功能? 也许我可以写到目前为止的作家字节?还是什么?

附注我无法使用 DownloadOperation(后台传输),因为来自服务器的数据请求证书 - 而且此功能在 DownloadOperations 中不存在。

【问题讨论】:

  • Windows.Web.Http.HttpClient 怎么样。那个支持进步。
  • Windows.Web.Http.HttpClient 在桌面上可用了吗?我以为它只是用于 Windows 商店应用程序。我从未见过任何人在现实生活中使用它。
  • Windows.Web.Http.HttpClient可以在ASP.Net中使用吗?
  • 此代码示例是android/xamarin 独有的?

标签: c# windows-8 download windows-runtime windows-8.1


【解决方案1】:

从 .Net 4.5 开始:使用 IProgress<T>

从 .Net 4.5 开始,您可以使用 IProgress<T> 接口处理异步进度报告。您可以使用HttpClient 编写用于下载文件的扩展方法,可以像这样调用其中progressIProgress<float> 的实现,用于您的进度条或其他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);
        }
    }
}

【讨论】:

  • 这不允许添加证书
  • 使用HttpClientHandler 创建HttpClient,并应用正确的证书选项,就像您在问题中所做的那样
  • 很好的答案。我所做的唯一修改是将cancellationToken 添加到您的HttpClientExtensions DownloadAsync 方法中的GetAsync 和CopyToAsync(非进度支持)调用中。
【解决方案2】:

这是一个独立的类,它将根据this SO answer 上的 TheBlueSkyeriksendc 上的代码进行下载并报告进度百分比this GitHub comment.

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)

【讨论】:

  • 必须删除以下内容才能获得进度更新:if (readCount % 100 == 0)
  • 知道为什么它不加载标题总大小吗?
  • @Nevaran 唯一的原因可能是服务器实际上并未返回该标头。尝试使用 Postman 对目标 URL 执行 GET,并查看它是否包含 Content-Length 标头。
  • 我不确定该怎么做——尽管它是我自己的 Google Drive 文件,如果这有助于解决问题
  • 这是使用 System.web.http 还是 System.net.http ?
【解决方案3】:

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

但如果出于某种原因您想坚持使用 System.Net,则需要实施自己的进度。

删除DataWriter,删除InMemoryRandomAccessStreamHttpCompletionOption.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();

【讨论】:

  • 谢谢,这样就可以了,但是有没有办法获得我将收到的总字节数?设置 ProgressBar 的最大值
  • 为什么不使用 ProgressMessageHandler msdn.microsoft.com/en-us/library/…
  • @thund 你会在这里找到处理程序和相关的类nuget.org/packages/Microsoft.AspNet.WebApi.Client/5.0.0
  • 您可以使用PostAsyncSystem.Net.Http.StreamContent。您使用自己实现的流初始化StreamContent,每次在流中调用 Stream.Read() 时,您都可以获得近似上传进度,因为您知道已读取了多少数据。
  • 您应该通过查看响应中的 Content-Length 标头来获取总字节数。即:response.Content.Headers.ContentLength
【解决方案4】:

以下代码显示了必须针对 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`
    }
}

上面并没有告诉你如何处理流,如何报告过程,或者尝试提供对原始问题中的代码的直接解决方案。但是,对于希望在他们的代码中应用进步的未来读者来说,这个答案可能更容易获得。

【讨论】:

  • 尽管有点不完整,但这是我能找到的唯一有效答案。 Windows.Web.Http 仅适用于 Windows,它也是一个 UWP 库,基本上是 .net 的噩梦反乌托邦版本
【解决方案5】:

与上述@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();
    }
}

【讨论】:

    【解决方案6】:

    René Sackers 版本非常好,但可能会更好。具体来说,它有一个微妙的竞争条件,由在流关闭之前触发 TriggerProgressChanged 引起。修复方法是在显式处理流后触发事件。下面的版本包含了上述更改,继承自 HttpClient 并添加了对取消令牌的支持。

    public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage);
    
    public class HttpClientWithProgress : HttpClient
    {
        private readonly string _DownloadUrl;
        private readonly string _DestinationFilePath;
    
        public event ProgressChangedHandler ProgressChanged;
    
        public HttpClientWithProgress(string downloadUrl, string destinationFilePath)
        {
            _DownloadUrl = downloadUrl;
            _DestinationFilePath = destinationFilePath;
        }
    
        public async Task StartDownload()
        {
            using (var response = await GetAsync(_DownloadUrl, HttpCompletionOption.ResponseHeadersRead))
                await DownloadFileFromHttpResponseMessage(response);
        }
    
        public async Task StartDownload(CancellationToken cancellationToken)
        {
            using (var response = await GetAsync(_DownloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
                await DownloadFileFromHttpResponseMessage(response);
        }
    
        private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response)
        {
            response.EnsureSuccessStatusCode();
            long? totalBytes = response.Content.Headers.ContentLength;
            using (var contentStream = await response.Content.ReadAsStreamAsync())
                await ProcessContentStream(totalBytes, contentStream);
        }
    
        private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream)
        {
            long totalBytesRead = 0L;
            long readCount = 0L;
            byte[] buffer = new byte[8192];
            bool isMoreToRead = true;
    
            using (FileStream fileStream = new FileStream(_DestinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
            {
                do
                {
                    int 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);
            }
            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);
        }
    }
    

    【讨论】:

      【解决方案7】:

      这是我对 René Sackers 答案的变体。主要区别:

      • 更实用的风格。
      • 只有一种方法而不是整个对象。
      • 可以取消下载
              public async static Task Download(
                     string downloadUrl,
                     string destinationFilePath,
                     Func<long?, long, double?, bool> progressChanged)
              {
                  using var httpClient = new HttpClient { Timeout = TimeSpan.FromDays(1) };
                  using var response = await httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead);
      
                  response.EnsureSuccessStatusCode();
                  var totalBytes = response.Content.Headers.ContentLength;
      
                  using var contentStream = await response.Content.ReadAsStreamAsync();
                  var totalBytesRead = 0L;
                  var readCount = 0L;
                  var buffer = new byte[8192];
                  var isMoreToRead = true;
      
                  static double? calculatePercentage(long? totalDownloadSize, long totalBytesRead) => totalDownloadSize.HasValue ? Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2) : null;
      
                  using var fileStream = new FileStream(destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
      
                  do
                  {
                      var bytesRead = await contentStream.ReadAsync(buffer);
                      if (bytesRead == 0)
                      {
                          isMoreToRead = false;
      
                          if (progressChanged(totalBytes, totalBytesRead, calculatePercentage(totalBytes, totalBytesRead)))
                          {
                              throw new OperationCanceledException();
                          }
      
                          continue;
                      }
      
                      await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead));
      
                      totalBytesRead += bytesRead;
                      readCount++;
      
                      if (readCount % 100 == 0)
                      {
                          if (progressChanged(totalBytes, totalBytesRead, calculatePercentage(totalBytes, totalBytesRead)))
                          {
                              throw new OperationCanceledException();
                          }
                      }
                  }
                  while (isMoreToRead);
              }
      

      可以这样调用:

          // Change this variable to stop the download
          // You can use a global variable or some kind of state management
          var mustStop = false;
      
          var downloadProgress = (long? _, long __, double? progressPercentage) =>
          {
             if (progressPercentage.HasValue)
                progressBar.Value = progressPercentage.Value;
      
             // In this example only the variable is checked
             // You could write other code that evaluates other conditions
             return mustStop;
          };
      
          SomeClass.Download("https://example.com/bigfile.zip", "c:\downloads\file.zip", downloadProgress);
      

      【讨论】:

      • 如何在进度条上调用和更新此方法?谢谢!
      • 编辑答案以包含使用示例
      【解决方案8】:

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

      【讨论】:

      • 如果你能提供一个例子我会很高兴
      • 很遗憾我没有 Win8 副本,所以我无法测试您的功能。然而,如果你想让事情变得相当简单,你可以使文件名和文件大小全局化,让一个带有循环和线程睡眠的后台工作人员定期检查文件大小,并更新进度条。然而,这不是一个非常优雅的解决方案。
      • 我认为这行不通。在ReadAsByteArrayAsync 返回之前,不会向流中写入任何内容。
      【解决方案9】:

      这是 René Sackers 答案的修改版本,具有以下功能更改:

      • http 客户端没有被释放(因为它不应该被释放)
      • 更好的进度处理
      • 回调创建httpRequest(自定义标头支持)
      • 利用 ArrayPool 减少内存占用
      • 自动事件订阅+取消订阅以防止事件处理程序导致内存泄漏

      用法:

      await DownloadWithProgress.ExecuteAsync(HttpClients.General, assetUrl, downloadFilePath, progressHandler, () =>
      {
          var requestMessage = new HttpRequestMessage(HttpMethod.Get, assetUrl);
          requestMessage.Headers.Accept.TryParseAdd("application/octet-stream");
          return requestMessage;
      });
      

      我想我不是唯一需要自定义标题的人,所以我想我会分享这个重写

      实施:

      public delegate void DownloadProgressHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage);
      
      public static class DownloadWithProgress
      {
          public static async Task ExecuteAsync(HttpClient httpClient, string downloadPath, string destinationPath, DownloadProgressHandler progress, Func<HttpRequestMessage> requestMessageBuilder = null)
          {
              requestMessageBuilder ??= GetDefaultRequestBuilder(downloadPath);
              var download = new HttpClientDownloadWithProgress(httpClient, destinationPath, requestMessageBuilder);
              download.ProgressChanged += progress;
              await download.StartDownload();
              download.ProgressChanged -= progress;
          }
      
          private static Func<HttpRequestMessage> GetDefaultRequestBuilder(string downloadPath)
          {
              return () => new HttpRequestMessage(HttpMethod.Get, downloadPath);
          }
      }
      
      internal class HttpClientDownloadWithProgress
      {
          private readonly HttpClient _httpClient;
          private readonly string _destinationFilePath;
          private readonly Func<HttpRequestMessage> _requestMessageBuilder;
          private int _bufferSize = 8192;
      
          public event DownloadProgressHandler ProgressChanged;
      
          public HttpClientDownloadWithProgress(HttpClient httpClient, string destinationFilePath, Func<HttpRequestMessage> requestMessageBuilder)
          {
              _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
              _destinationFilePath = destinationFilePath ?? throw new ArgumentNullException(nameof(destinationFilePath));
              _requestMessageBuilder = requestMessageBuilder ?? throw new ArgumentNullException(nameof(requestMessageBuilder));
          }
      
          public async Task StartDownload()
          {
              using var requestMessage = _requestMessageBuilder.Invoke();
              using var response = await _httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead);
              await DownloadAsync(response);
          }
      
          private async Task DownloadAsync(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 = ArrayPool<byte>.Shared.Rent(_bufferSize);
              var isMoreToRead = true;
      
              using (var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, _bufferSize, true))
              {
                  do
                  {
                      var bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);
                      if (bytesRead == 0)
                      {
                          isMoreToRead = false;
                          ReportProgress(totalDownloadSize, totalBytesRead);
                          continue;
                      }
      
                      await fileStream.WriteAsync(buffer, 0, bytesRead);
      
                      totalBytesRead += bytesRead;
                      readCount += 1;
      
                      if (readCount % 100 == 0)
                          ReportProgress(totalDownloadSize, totalBytesRead);
                  }
                  while (isMoreToRead);
              }
      
              ArrayPool<byte>.Shared.Return(buffer);
          }
      
          private void ReportProgress(long? totalDownloadSize, long totalBytesRead)
          {
              double? progressPercentage = null;
              if (totalDownloadSize.HasValue)
                  progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2);
      
              ProgressChanged?.Invoke(totalDownloadSize, totalBytesRead, progressPercentage);
          }
      }
      

      【讨论】:

      • 经过 7 年的提问 - 这仍然是实际的 =)
      猜你喜欢
      • 2014-05-20
      • 2014-02-03
      • 2023-03-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多