进度条与HttpClient

Che*_*ese 24 c# download windows-8 windows-runtime windows-8.1

我有一个文件下载功能:

        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();
Run Code Online (Sandbox Code Playgroud)

我怎样才能实现进度条功能?也许到目前为止我可以写出写入的字节数?或者其他的东西?

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

kie*_*wic 24

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

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

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

// 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();
Run Code Online (Sandbox Code Playgroud)

  • 为什么不使用ProgressMessageHandler http://msdn.microsoft.com/en-us/library/system.net.http.handlers.progressmessagehandler(v=vs.118).aspx? (5认同)
  • 您应该通过查看响应中的`Content-Length`标头来获取总字节数.即:`response.Content.Headers.ContentLength` (3认同)
  • 谢谢,这样做,但有没有办法获得我将收到的总字节数?设置ProgressBar的最大值 (2认同)
  • 你可以使用`PostAsync`和`System.Net.Http.StreamContent`.您使用由您实现的流初始化`StreamContent`,并且每次在流中调用Stream.Read()时都可以获得**近似**上载进度,因为您知道已读取了多少数据. (2认同)

Ren*_*ers 23

这是一个自包含的类,它将进行下载,并根据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();
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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();
}
Run Code Online (Sandbox Code Playgroud)

结果:

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)
Run Code Online (Sandbox Code Playgroud)


Bru*_*ell 19

从.Net 4.5开始:使用 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

履行

此扩展方法的代码如下所示.请注意,此扩展依赖于另一个扩展来处理具有进度报告的异步流复制.

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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

通过流扩展实现真正的进度报告:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 使用`HttpClientHandler`创建`HttpClient`,其中应用了正确的证书选项,就像您在问题中所做的那样 (3认同)

cae*_*say 10

实现上传和下载进度跟踪的最简单方法是使用Microsoft.AspNet.WebApi.Client nuget 包中的ProgressMessageHandler

注:该库原名为System.Net.Http.Formatting,后更名Microsoft.AspNet.WebApi.Client. 但是,该库与 ASP.Net 无关,任何寻求 Microsoft 官方扩展的项目都可以使用HttpClient. 源代码可以在这里找到。

例子:

var handler = new HttpClientHandler() { AllowAutoRedirect = true };
var ph = new ProgressMessageHandler(handler);

ph.HttpSendProgress += (_, args) =>
{
    Console.WriteLine($"upload progress: {(double)args.BytesTransferred / args.TotalBytes}");
};

ph.HttpReceiveProgress += (_, args) =>
{
    Console.WriteLine($"download progress: {(double)args.BytesTransferred / args.TotalBytes}");
};

var client = new HttpClient(ph);
await client.SendAsync(...);
Run Code Online (Sandbox Code Playgroud)

请注意,如果上传字节数组,这不会报告进度。请求消息内容必须是流。

  • 这与ASP无关。此 nuget 包包含命名空间“System.Net.Http.Formatting”,它只是“System.Net.Http”的内容扩展。任何寻求 HttpClient 官方扩展的项目都可以(并且应该)使用它。 (3认同)
  • 迄今为止最简单、最简单的解决方案! (3认同)

vos*_*d01 7

以下代码显示了必须对HttpClientapi 执行哪些操作才能获取下载进度的最小示例。

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`
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Jam*_*don 6

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

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

可以这样调用:

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


Bal*_*age 5

与上面的 @Ren\xc3\xa9 Sackers 解决方案相同,但添加了取消下载的功能

\n\n
class HttpClientDownloadWithProgress : IDisposable\n{\n    private readonly string _downloadUrl;\n    private readonly string _destinationFilePath;\n    private readonly CancellationToken? _cancellationToken;\n\n    private HttpClient _httpClient;\n\n    public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage);\n\n    public event ProgressChangedHandler ProgressChanged;\n\n    public HttpClientDownloadWithProgress(string downloadUrl, string destinationFilePath, CancellationToken? cancellationToken = null)\n    {\n        _downloadUrl = downloadUrl;\n        _destinationFilePath = destinationFilePath;\n        _cancellationToken = cancellationToken;\n    }\n\n    public async Task StartDownload()\n    {\n        _httpClient = new HttpClient { Timeout = TimeSpan.FromDays(1) };\n\n        using (var response = await _httpClient.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead))\n            await DownloadFileFromHttpResponseMessage(response);\n    }\n\n    private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response)\n    {\n        response.EnsureSuccessStatusCode();\n\n        var totalBytes = response.Content.Headers.ContentLength;\n\n        using (var contentStream = await response.Content.ReadAsStreamAsync())\n            await ProcessContentStream(totalBytes, contentStream);\n    }\n\n    private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream)\n    {\n        var totalBytesRead = 0L;\n        var readCount = 0L;\n        var buffer = new byte[8192];\n        var isMoreToRead = true;\n\n        using (var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))\n        {\n            do\n            {\n                int bytesRead;\n                if (_cancellationToken.HasValue)\n                {\n                    bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, _cancellationToken.Value);\n                }\n                else\n                {\n                    bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);\n                }\n\n                if (bytesRead == 0)\n                {\n                    isMoreToRead = false;\n                    continue;\n                }\n\n                await fileStream.WriteAsync(buffer, 0, bytesRead);\n\n                totalBytesRead += bytesRead;\n                readCount += 1;\n\n                if (readCount % 10 == 0)\n                    TriggerProgressChanged(totalDownloadSize, totalBytesRead);\n            }\n            while (isMoreToRead);\n\n        }\n\n        //the last progress trigger should occur after the file handle has been released or you may get file locked error\n        TriggerProgressChanged(totalDownloadSize, totalBytesRead);\n    }\n\n    private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead)\n    {\n        if (ProgressChanged == null)\n            return;\n\n        double? progressPercentage = null;\n        if (totalDownloadSize.HasValue)\n            progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2);\n\n        ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage);\n    }\n\n    public void Dispose()\n    {\n        _httpClient?.Dispose();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n


Sha*_*aun 5

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

\n\n
public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage);\n\npublic class HttpClientWithProgress : HttpClient\n{\n    private readonly string _DownloadUrl;\n    private readonly string _DestinationFilePath;\n\n    public event ProgressChangedHandler ProgressChanged;\n\n    public HttpClientWithProgress(string downloadUrl, string destinationFilePath)\n    {\n        _DownloadUrl = downloadUrl;\n        _DestinationFilePath = destinationFilePath;\n    }\n\n    public async Task StartDownload()\n    {\n        using (var response = await GetAsync(_DownloadUrl, HttpCompletionOption.ResponseHeadersRead))\n            await DownloadFileFromHttpResponseMessage(response);\n    }\n\n    public async Task StartDownload(CancellationToken cancellationToken)\n    {\n        using (var response = await GetAsync(_DownloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken))\n            await DownloadFileFromHttpResponseMessage(response);\n    }\n\n    private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response)\n    {\n        response.EnsureSuccessStatusCode();\n        long? totalBytes = response.Content.Headers.ContentLength;\n        using (var contentStream = await response.Content.ReadAsStreamAsync())\n            await ProcessContentStream(totalBytes, contentStream);\n    }\n\n    private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream)\n    {\n        long totalBytesRead = 0L;\n        long readCount = 0L;\n        byte[] buffer = new byte[8192];\n        bool isMoreToRead = true;\n\n        using (FileStream fileStream = new FileStream(_DestinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))\n        {\n            do\n            {\n                int bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);\n                if (bytesRead == 0)\n                {\n                    isMoreToRead = false;\n                    continue;\n                }\n\n                await fileStream.WriteAsync(buffer, 0, bytesRead);\n\n                totalBytesRead += bytesRead;\n                readCount += 1;\n\n                if (readCount % 10 == 0)\n                    TriggerProgressChanged(totalDownloadSize, totalBytesRead);\n            }\n            while (isMoreToRead);\n        }\n        TriggerProgressChanged(totalDownloadSize, totalBytesRead);\n    }\n\n    private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead)\n    {\n        if (ProgressChanged == null)\n            return;\n\n        double? progressPercentage = null;\n        if (totalDownloadSize.HasValue)\n            progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2);\n\n        ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n