0

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

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")
                                )
                            );
                        }
                }
            }
        }
4

1 回答 1

2

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

要让 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;
                }
            }
    }
}

你会在这里注意到三件事。首先,使用来同步访问下载队列,避免潜在的并发访问和竞争条件。请注意,使用_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 对象。


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

于 2013-10-29T02:03:33.397 回答