带有 HttpClient 的进度条

Posted

技术标签:

【中文标题】带有 HttpClient 的进度条【英文标题】:Progress bar with 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 独有的? 【参考方案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 年的提问 - 这仍然是实际的 =)

以上是关于带有 HttpClient 的进度条的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Apache HttpClient 4 获取文件上传的进度条?

Android学习笔记--使用Apache HttpClient实现网络下载效果,附带进度条显示

使用HttpClient的进度信息

如何使用 C# HttpClient PostAsync 显示上传进度

短信接口

HttpClient