【问题标题】:How can I display each file download progress in a progressBar?如何在进度条中显示每个文件的下载进度?
【发布时间】:2013-11-04 05:29:35
【问题描述】:

这是我做了所有更改后的新类代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Net;
using unfreez_wrapper;
using System.Drawing;
using System.Globalization;

namespace WeatherMaps
{


    class ExtractImages
    {
        int count = 0;
        int length;
        string stringForSatelliteMapUrls = "http://www.sat24.com/";
        static int counter;
        UnFreezWrapper uf;
        List<string> imagesSatelliteUrls;
        List<string> imagesRainUrls;
        string localdir;

        // Instance with one List and Files and Animation
        public ExtractImages(List<string> mapToRead, string LocalFileDir, string UrlsDir)
        {
            counter = 0;
        }

        // Instance with more then one List and Files and Animation
        public ExtractImages(Queue<DownloadData> downloadQueue, List<string> FirstTags, List<string> LastTags, List<string> Maps, string LocalFileDir, string UrlsDir)
    {
        localdir = LocalFileDir;
        counter = 0;
        imagesSatelliteUrls = new List<string>();
        imagesRainUrls = new List<string>();
        int startIndex = 0;
        int endIndex = 0;
        int position = 0;
        for (int i = 0; i < Maps.Count; i++)
        {
            imagesSatelliteUrls.Add("Group " + (i + 1));
            string startTag = FirstTags[i];
            string endTag = LastTags[i];
            startIndex = Maps[i].IndexOf(startTag);
            while (startIndex > 0)
            {

                endIndex = Maps[i].IndexOf(endTag, startIndex);
                if (endIndex == -1)
                {
                    break;
                }
                string t = Maps[i].Substring(startIndex, endIndex - startIndex + endTag.Length);
                imagesSatelliteUrls.Add(t);
                position = endIndex + endTag.Length;
                startIndex = Maps[i].IndexOf(startTag, position);

            }
                string imageSatelliteUrl = imagesSatelliteUrls[i];
                if (!imagesSatelliteUrls[i].StartsWith("Group"))
                {
                    if (!imagesSatelliteUrls[i].StartsWith("http://"))
                    {
                        imagesSatelliteUrls[i] = "http://" + imagesSatelliteUrls[i];
                        imageSatelliteUrl = imagesSatelliteUrls[i];
                    }
                    if (!imagesSatelliteUrls[i].Contains("href"))
                    {
                        downloadQueue.Enqueue(
                            new DownloadData(
                                new Uri(imageSatelliteUrl),
                                UrlsDir + "SatelliteImage" + i.ToString("D6")
                            )
                        );
                    }
            }
        }
    }

        public class DownloadData
        {
            public Uri DownloadUri;
            public string TargetPath;

            public DownloadData(Uri downloadUri, string targetPath)
            {
                this.DownloadUri = downloadUri;
                this.TargetPath = targetPath;
            }
        }

由于在变量列表中我有每个索引一个字符串“组”,我必须添加这个过滤器。 另外 imageSatelliteUrl 列表中的链接不是以 http:// 开头的,所以我也添加了这个过滤器,但我不确定它是否是好方法。

在 Form1 中我做了:

在 Form1 的顶部我做了:

private WebClient _webClient = null;
private readonly Queue<ExtractImages.DownloadData> _downloadQueue = new Queue<ExtractImages.DownloadData>();
ExtractImages ei;

在 Form1 的构造函数中我做了:

InitializeWebClient();

然后在 Form1 的后台工作人员的 DoWork 事件中,我做了:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            lock (_downloadQueue)
            {
                ei = new ExtractImages(_downloadQueue, StartTags, LastTags, Maps, localFilename, UrlsPath);

                if (_downloadQueue.Count > 0)
                    foreach (ProgressBar pb in progressbars)
                    {
                        if (pb.Tag == null)
                        {
                            ExtractImages.DownloadData dd = _downloadQueue.Dequeue();
                            pb.Tag = dd;
                            _webClient.DownloadFileAsync(
                                dd.DownloadUri,
                                dd.TargetPath,
                                pb
                            );

                            if (_downloadQueue.Count == 0) break;
                        }
                    }
            }
        }

然后在Form1中我添加了这个方法:

private void InitializeWebClient()
        {
            _webClient = new WebClient();
            _webClient.DownloadFileCompleted += DownloadCompletedCallback;
            _webClient.DownloadProgressChanged += DownloadProgressCallback;
        }

最后添加了这两个事件:

private void DownloadCompletedCallback(object sender, AsyncCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                //... download cancelled...
            }
            else if (e.Error != null)
            {
                //... download failed...
            }

            ProgressBar pb = e.UserState as ProgressBar;

            lock (_downloadQueue)
            {
                if (_downloadQueue.Count == 0)
                {
                    if (pb != null) pb.Tag = null;
                }
                else
                {
                    ExtractImages.DownloadData dd = _downloadQueue.Dequeue();
                    if (pb != null) pb.Tag = dd;
                    _webClient.DownloadFileAsync(
                        dd.DownloadUri,
                        dd.TargetPath,
                        e.UserState
                    );
                }
            }
        }

        private void DownloadProgressCallback(object sender, DownloadProgressChangedEventArgs e)
        {
            ProgressBar pb = e.UserState as ProgressBar;
            if (pb != null) pb.Value = e.ProgressPercentage;
        }

但它的作用是下载一个我使用断点的文件,然后它停止或不继续下载,并且我在进度条中看不到任何内容。

编辑**

现在的方法 ExtractImages:

public ExtractImages(Queue<DownloadData> downloadQueue, List<string> FirstTags, List<string> LastTags, List<string> Maps, string LocalFileDir, string UrlsDir)
        {
            localdir = LocalFileDir;
            counter = 0;
            imagesSatelliteUrls = new List<string>();
            imagesRainUrls = new List<string>();
            int startIndex = 0;
            int endIndex = 0;
            int position = 0;
            for (int i = 0; i < Maps.Count; i++)
            {
                imagesSatelliteUrls.Add("Group " + (i + 1));
                counter++;
                string startTag = FirstTags[i];
                string endTag = LastTags[i];
                startIndex = Maps[i].IndexOf(startTag);
                while (startIndex > 0)
                {

                    endIndex = Maps[i].IndexOf(endTag, startIndex);
                    if (endIndex == -1)
                    {
                        break;
                    }
                    string t = Maps[i].Substring(startIndex, endIndex - startIndex + endTag.Length);
                    imagesSatelliteUrls.Add(t);
                    position = endIndex + endTag.Length;
                    startIndex = Maps[i].IndexOf(startTag, position);

                }
                    string imageSatelliteUrl = imagesSatelliteUrls[i];
                    if (!imagesSatelliteUrls[i].StartsWith("Group"))
                    {
                        if (!imagesSatelliteUrls[i].StartsWith("http://"))
                        {
                            imagesSatelliteUrls[i] = "http://" + imagesSatelliteUrls[i];
                            imageSatelliteUrl = imagesSatelliteUrls[i];
                        }
                        if (!imagesSatelliteUrls[i].Contains("href"))
                        {
                            downloadQueue.Enqueue(
                                new DownloadData(
                                    new Uri(imageSatelliteUrl),
                                    UrlsDir + "SatelliteImage" + counter.ToString("D6")
                                )
                            );
                        }
                }
            }
        }

【问题讨论】:

  • WebClient 为此目的提供了 DownloadProgressChanged 事件 - 使用它。请注意,仅当您使用 WebClient 提供的 *Async 方法时,使用此事件才有意义。
  • 你的代码现在显示什么?
  • 什么都没有。进度条是空的。它只下载新类方法中的文件就是这样。目前我还没有显示任何内容。
  • 我将client.DownloadFile改为Async:Uri uri = new Uri(imagesSatelliteUrls[x]); client.DownloadFileAsync(uri, UrlsDir + "SatelliteImage" + x.ToString("D6"));现在我需要将下载文件的进度传递给progressBars。
  • 好的,我将用我现在对 webclient 所做的事情来更新我的问题!

标签: c# winforms


【解决方案1】:

注意:为了让我的答案易于阅读,我省略了任何类型的异常处理。但是,在您的真实代码中,您必须处理异常处理以及涉及下载失败或中断的情况!

要让 8 个并行下载在各自的 8 个进度条中显示其进度,您只能从 8 个活动下载开始,任何进一步的现有下载作业都被视为待处理。当其中一个活动下载完成时,将启动另一个挂起的下载作业,同时将其与用于完成下载的进度条相关联。 (这里给出的代码示例可以使用任意数量的进度条进行操作,而不仅仅是 8 个。)

所有挂起的下载作业都将存储在一个队列中。要开始下载,需要从队列中取出下载作业。

private readonly Queue<DownloadData> _downloadQueue = new Queue<DownloadData>();

(注意private readonly,它允许将此对象用作同步对象,稍后会解释。)

DownloadData 是一个简单的类型,其中包含每个下载作业的所有必要信息。

public class DownloadData
{
    public Uri DownloadUri;
    public string TargetPath;

    public DownloadData(Uri downloadUri, string targetPath)
    {
        this.DownloadUri = downloadUri;
        this.TargetPath = targetPath;
    }
}


现在,这个队列是如何被填满的?该代码基本上已经存在于您的 ExtractImages 类中。但不是填充两个列表 imagesSatelliteUrlsimagesRainUrls,而是填充队列:

public static void AddImageDownloadsToQueue(Queue<DownloadData> downloadQueue, List<string> FirstTags, List<string> LastTags, List<string> Maps, string LocalFileDir, string UrlsDir)
{
    for (int i = 0; i < Maps.Count; i++)
    {
        string imageSatelliteUrl = ... // compose URL for satellite image

        downloadQueue.Enqueue(
            new DownloadData(
                new Uri(imageSatelliteUrl),
                UrlsDir + "SatelliteImage" + x.ToString("D6")
            )
        );

        string imageRainUrl = ... // compose URL for rain image

        downloadQueue.Enqueue(
            new DownloadData(
                new Uri(imageRainUrl),
                UrlsDir + "RainImage" + x.ToString("D6")
            )
        );
    }
}


使用填充队列的方法,只需要调用它并最终处理队列。下载队列的处理发生在两个场合/两个地方:(1)如果一个活动下载完成,则必须启动另一个下载项目,以及(2)根据可用进度的数量最初启动多个下载栏(=下载插槽)。

稍后将介绍前者。后者连同调用 AddImageDownloadsToQueue 方法将发生在 backgroundWorker1_DoWork

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    lock(_downloadQueue)
    {
        AddImageDownloadsToQueue(_downloadQueue, StartTags, LastTags, Maps, localFilename, UrlsPath);

        if (_downloadQueue.Count > 0)
            foreach (ProgressBar pb in progressbars)
            {
                if (pb.Tag == null)
                {
                    DownloadData dd = _downloadQueue.Dequeue();
                    StartDownloadWithProgressBar(dd, pb);

                    if (_downloadQueue.Count == 0) break;
                }
            }
    }
}

您会在这里注意到三件事。首先,使用 lock 来同步访问下载队列,避免潜在的并发访问和竞争条件。请注意,使用 _downloadQueue 作为锁定对象是安全的,而且只有在它被声明为 private readonly 时才安全。

其次,测试 if (pb.Tag == null) 完成。 ProgressBar 的 Tag 属性在这里被(ab)用作标志,以指示进度条是否已用于下载。如果它的值为空,则表示它没有被使用。任何其他值(非空)表示,它当前用于下载。假设您可能会将下载项目分成几个块排队,这种方法可确保始终使用所有可用的进度条,无论何时将新的下载项目添加到队列中。

第三个也是最重要的方法是StartDownloadWithProgressBar。此方法设置一个 WebClient 对象并开始下载。

private void StartDownloadWithProgressBar(ExtractImages.DownloadData downloadData, ProgressBar progressBar)
{
    WebClient wc = new WebClient();
    wc.DownloadFileCompleted += DownloadCompletedCallback;
    wc.DownloadProgressChanged += DownloadProgressCallback;

    ActiveDownloadJob adJob = new ActiveDownloadJob(downloadData, progressBar, wc);
    progressBar.Tag = adJob;
    wc.DownloadFileAsync(
        downloadData.DownloadUri,
        downloadData.TargetPath,
        adJob
    );
}

虽然 WebClient.DownloadFileAsync 用于下载文件,但单个 WebClient 对象无法处理多个并发下载。因此,每个下载都需要自己的 WebClient 实例。两个回调处理 WebClient 的 DownloadFileCompleted 和 DownloadProgressChanged 事件。稍后将介绍这些回调。

创建了一个 ActiveDownloadJob 对象,该对象跟踪下载作业及其关联的 ProgressBar 和 WebClient(尽管下面概述的示例代码不需要再次访问 WebClient,但它可能适用于未来的扩展以获取手头 WebClient 实例的引用)。

ActiveDownloadJob 分配给进度条的Tag 属性,这样做是为了表明进度条正在使用中。此外,ActiveDownloadJob 对象作为 UserToken 参数传递给 DownloadFileAsync 方法。这样做是为了让回调方法在(正在进行的)下载调用时知道要操作哪个进度条。

ActiveDownloadJob 是一个非常简单的类:

    class ActiveDownloadJob
    {
        public DownloadData DownloadData;
        public ProgressBar ProgressBar;
        public WebClient WebClient;

        public ActiveDownloadJob(ExtractImages.DownloadData downloadData, ProgressBar progressBar, WebClient webClient)
        {
            this.DownloadData = downloadData;
            this.ProgressBar = progressBar;
            this.WebClient = webClient;
        }
    }


下载完成的回调方法 (DownloadCompletedCallback) 必须注意两件事:检查已取消/失败的下载并开始队列中的下一个下载作业。

private void DownloadCompletedCallback(object sender, AsyncCompletedEventArgs e)
{
    if (e.Cancelled)
    {
         ... download cancelled...
    }
    else if (e.Error != null)
    {
         ... download failed...
    }

    ActiveDownloadJob adJob = e.UserState as ActiveDownloadJob;
    ProgressBar pb = (adJob != null) ? adJob.ProgressBar : null;

    lock (_downloadQueue)
    {
        if (_downloadQueue.Count == 0)
        {
            if (pb != null) pb.Tag = null;
        }
        else
        {
            DownloadData dd = _downloadQueue.Dequeue();
            StartDownloadWithProgressBar(dd, pb);
        }
    }
}

请注意,对队列的访问是通过锁定到与 backgroundWorker1_DoWork 方法中发生的相同同步对象来同步的。此外,请注意此方法如何获取下载的 ActiveDownloadJob 对象实例。还记得 ActiveDownloadJob 对象作为 UserToken 参数传递给 DownloadFileAsync 方法吗?在这里它再次浮出水面。

如果队列中没有进一步的下载作业,进度条的Tag属性设置为null,表示ProgressBar空闲且再次可用,否则将进行新的下载作业从队列中并通过调用 StartDownloadWithProgressBar 开始。 StartDownloadWithProgressBar 会将进度条的 Tag 属性设置为新下载的 ActiveDownloadJob 对象。

无论哪种情况,始终确保完成下载的 ActiveDownloadJob 对象从进度条的 Tag 属性中删除,因此它和保存在其中的 WebClient 最终将被 GC'ed .


虽然我们快完成了,但仍然缺少一些内容:下载过程中需要更新进度条。这发生在下载进度的回调方法中:

private void DownloadProgressCallback(object sender, DownloadProgressChangedEventArgs e)
{
    ActiveDownloadJob adJob = e.UserState as ActiveDownloadJob;
    if (adJob != null && adJob.ProgressBar != null)
        adJob.ProgressBar.Invoke((Action) (() => adJob.ProgressBar.Value = e.ProgressPercentage));
}

DownloadProgressCallback 以与 DownloadCompletedCallback 类似的方式获取属于下载的 ProgressBar 对象。


如果您需要知道所有下载何时完成,这里有一个提示:如果队列为空并且没有活动下载,则所有下载都已完成(或者,没有开始下载......)

【讨论】:

  • Elgonzo 我刚刚更新了我的问题,现在添加了我在新类和 Form1 中所做的代码。在新类中,我看到 List imageSatelliteUrl 包含 260 个链接,因为它应该是。但我在这一行使用了断点 downloadQueue.Enqueue(new DownloadData(new Uri(imageSatelliteUrl), UrlsDir + "SatelliteImage" + i.ToString("D6") ));我点击继续,它一直停在那里直到列表填充到 260 个链接,仅此而已。
  • 在 7 次迭代/循环填充 260 个链接之后填充列表后,就是这样。它回到程序,没有任何反应。我在进度条上看不到任何内容。
  • Webclient.DownloadFileAsync 真的被调用了吗?它可能会引发异常吗?
  • form1 上的 Webclient.DownloadFileAsync 确实调用了,但我现在看到里面的 uri 导致不存在页面。并且下载到我的硬盘上的文件是 okb 大小。例如这是一个 uri:image2.ashx/…
  • 我会检查的。这个链接怎么下载。其他都可以,但是这个不起作用。我将为此提出一个新问题。也许这就是为什么progressBars 什么都不起作用的问题?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-30
  • 1970-01-01
  • 1970-01-01
  • 2012-02-23
  • 2021-10-26
相关资源
最近更新 更多