winforms 如何使用带有进度栏的HttpClient下载文件?

ilmyapht  于 2022-11-25  发布在  其他
关注(0)|答案(1)|浏览(120)

我创建了一个名为SiteDownload的新类,并添加了一些下载图像的链接:

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

    public class SiteDownload
    {
        public static List<string> Sites()
        {
            List<string> list = new List<string>();

            list.Add("mysite.com/sites/default/files/1231105.gif");
            list.Add("mysite.com/sites/default/files/1231040.gif");

        return list;
        }

        public static async Task<List<Website>> ParallelDownload(IProgress<ProgressReport> progress, CancellationTokenSource cancellationTokenSource)
        {
            List<string> sites = Sites();
            List<Website> list = new List<Website>();
            ProgressReport progressReport = new ProgressReport();
            ParallelOptions parallelOptions = new ParallelOptions();
            parallelOptions.MaxDegreeOfParallelism = 8;
            parallelOptions.CancellationToken = cancellationTokenSource.Token;
            await Task.Run(() =>
            {
                try
                {
                    Parallel.ForEach<string>(sites, parallelOptions, (site) =>
                    {
                        Website results = Download(site);
                        list.Add(results);
                        progressReport.SitesDownloaded = list;
                        progressReport.PercentageComplete = (list.Count * 100) / sites.Count;
                        progress.Report(progressReport);
                        parallelOptions.CancellationToken.ThrowIfCancellationRequested();
                    });
                }
                catch (OperationCanceledException ex)
                {
                    throw ex;
                }
            });

            return list;
        }

        private static Website Download(string url)
        {
            Website website = new Website();
            WebClient client = new WebClient();
            website.Url = url;
            website.Data = client.DownloadString(url);
            return website;
        }

        public class Website
        {
            public string Url { get; set; }
            public string Data { get; set; }
        }

        public class ProgressReport
        {
            public int PercentageComplete { get; set; }
            public List<Website> SitesDownloaded { get; set; }
        }
    }

form1中:

using System.Linq;
using System.Net;
using System.Threading.Tasks;
using static HttpClientFilesDownloader.SiteDownload;

namespace HttpClientFilesDownloader
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        void PrintResults(List<Website> results)
        {
            richTextBox1.Text = string.Empty;
            foreach (var item in results)
                richTextBox1.Text += $"{item.Url} downloaded: {item.Data.Length} characters long.{Environment.NewLine}";
        }

        void ReportProgress(object sender, ProgressReport e)
        {
            progressBar1.Value = e.PercentageComplete;
            label1.Text = $"Completed: {e.PercentageComplete} %";
            PrintResults(e.SitesDownloaded);
        }

        CancellationTokenSource cancellationTokenSource;
        private async void button1_Click(object sender, EventArgs e)
        {
            try
            {
                cancellationTokenSource = new CancellationTokenSource();
                Progress<ProgressReport> progress = new Progress<ProgressReport>();
                progress.ProgressChanged += ReportProgress;
                var watch = Stopwatch.StartNew();
                var results = await SiteDownload.ParallelDownload(progress, cancellationTokenSource);
                PrintResults(results);
                watch.Stop();
                var elapsed = watch.ElapsedMilliseconds;
                richTextBox1.Text += $"Total execution time: {elapsed}";
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                cancellationTokenSource.Dispose();
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (cancellationTokenSource != null)
                cancellationTokenSource.Cancel();
        }
    } 
}

设计者

当我点击START按钮时,没有任何React。我没有看到progressBar得到任何进程,label1没有更新,RichTextBox中没有任何内容。它只是没有下载图像。
我没有收到任何错误,只是无法下载。
我把这个例子从这个网站,而不是下载网站/s我试图下载图像文件,并将它们保存在硬盘上的图像:
example
我还需要像使用webclient一样添加header:

webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0 Chrome");

但不确定如何将头添加到HttpClient。

uemypmqf

uemypmqf1#

HTTP资源下载器的示例。
这个类是针对.Net 6+的,因为它使用Parallel.ForEachAsync()
record keyword需要C# 9+. Nullable已启用
我尽量保持你在OP中使用的结构
若要开始下载资源集合,请调用静态Download()方法,传递IProgress<ProgressReport>委托、表示资源URL的字符串集合和CancellationTokenSource
ReportProgress()方法会将ProgressReport记录封送行程至UI执行绪。它会指涉网站记录,其中包含目前资源的URL、影像(在此范例中)字节、Completed状态,以及资源因某种原因下载失败时所掷回的例外状况。如果同时取消下载,例外状况原因将是The operation was canceled
它还以百分比的形式返回下载的总体进度。
请注意,取消操作时进度过程也会完成,因为您可能想知道在取消操作之前哪些资源已完成,哪些资源无法完成

**注意:**静态Download()方法不是线程安全的,即您不能并发调用此方法,例如,同时下载多个资源列表。

再次调用该方法之前,请检查IsBusy属性。

public class ResourceDownloader {

    private static Lazy<HttpClient> client = new(() => {
        HttpClientHandler handler = CreateHandler(autoRedirect: true);

        var client = new HttpClient(handler, true) { Timeout = TimeSpan.FromSeconds(60) };
        client.DefaultRequestHeaders.Add("User-Agent", @"Mozilla/5.0 (Windows NT 10; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0");
        client.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
        client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
        client.DefaultRequestHeaders.ConnectionClose = true;
        return client;
    }, true);

    private static HttpClientHandler CreateHandler(bool autoRedirect)
    {
        return new HttpClientHandler() {
            AllowAutoRedirect = autoRedirect,
            CookieContainer = new CookieContainer(),
            AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
        };
    }

    public record Website(string Url, byte[]? Data, bool Completed = true, Exception? Ex = null);
    public record ProgressReport(Website Site, int PercentageComplete);

    private static object syncObj = new object();
    private static ConcurrentBag<Website> processed = default!;
    private static int progressCount = 0;
    private static int totalCount = 0;

    public static bool IsBusy { get; internal set; } = false;

    public static async Task<List<Website>> Download(IProgress<ProgressReport> progress, IList<string> sites, CancellationTokenSource cts)
    {
        IsBusy = true;
        processed = new ConcurrentBag<Website>();
        progressCount = 0;
        totalCount = sites.Count;

        try {
            ParallelOptions options = new() {
                MaxDegreeOfParallelism = 8,
                CancellationToken = cts.Token
            };

            await Parallel.ForEachAsync(sites, options, async (site, token) => {
                try {
                    var dataBytes = await client.Value.GetByteArrayAsync(site, token);
                    ReportProgress(progress, dataBytes, site, null);
                }
                catch (Exception ex) {
                    ReportProgress(progress, null, site, ex);
                }
            });
        }
         // To Debug / Log
        catch (TaskCanceledException) { Debug.Print("The operation was canceled"); }
        finally { IsBusy = false; }
        return processed.ToList();
    }

    private static void ReportProgress(IProgress<ProgressReport> progress, byte[]? data, string site, Exception? ex) {
        lock (syncObj) {
            progressCount += 1;
            var percentage = progressCount * 100 / totalCount;
            Website website = new(site, data, ex is null, ex);
            processed.Add(website);
            progress.Report(new ProgressReport(website, percentage));
        }
    }
}

您可以按如下方式设置表单:

  • 添加一个TextBox(此处名为logger)以显示正在下载的资源的状态
  • 用于开始下载的按钮(名为btnStartDownload
  • 用于取消下载的按钮(名为btnStopDownload
  • ProgressBar(名为progressBar),用于显示总体进度

请注意,使用活动(未配置)调试器时,您可能会收到抛出异常的通知,因此可以使用CTRL + F5运行项目

public partial class SomeForm : Form {
    public SomeForm() => InitializeComponent();

    internal List<string> Sites()
    {
        var list = new List<string>();

        list.Add("https://somesite/someresource.jpg");
        // [...] add more URLs
        return list;
    }

    IProgress<ResourceDownloader.ProgressReport>? downloadProgress = null;
    CancellationTokenSource? cts = null;

    private void Updater(ResourceDownloader.ProgressReport progress)
    {
        StringBuilder log = new(1024);
        if (progress.Site.Completed) {
            log.Append($"Success! \t {progress.Site.Url}\r\n");
        }
        else {
            log.Append($"Failed! \t {progress.Site.Url}\r\n");
            log.Append($"\tReason: {progress.Site.Ex?.Message}\r\n");
        }
        logger.AppendText(log.ToString());
        progressBar.Value = progress.PercentageComplete;
    }

    private async void btnStartDownload_Click(object sender, EventArgs e)
    {
        if (ResourceDownloader.IsBusy) return;
        var sites = Sites();

        // This collection will contain the status (and data) of all downloads in th end
        List<ResourceDownloader.Website>? downloads = null;

        using (cts = new CancellationTokenSource()) {
            downloadProgress = new Progress<ResourceDownloader.ProgressReport>(Updater);
            downloads = await ResourceDownloader.Download(downloadProgress, sites, cts);
        }
    }

    private void btnStopDownload_Click(object sender, EventArgs e) => cts?.Cancel();
}

它是这样工作的:

相关问题