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 の backgroundworker の 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;
        }

最後に、次の 2 つのイベントを追加しました。

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

しかし、ブレークポイントを使用して1つのファイルをダウンロードすると、ダウンロードが停止するか、ダウンロードが続行されず、プログレスバーに何も表示されません。

編集**

現在の 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

NOTE: To keep my answer readable, i omitted any kind of exception handling. In your real code, however, you MUST take care of exception handling and situations involving failed or interrupted downloads!

To have 8 parallel downloads showing their progress in the respective 8 progress bars, you can only begin with 8 active downloads, any further existing download jobs are considered pending. When one of the active downloads finishes, another one of the pending download jobs will be started while associating it with the progress bar that was used for the finished download. (The code examples given here are done in a way to operate with any number of progress bars, not just only with 8.)

All pending download jobs will be stored in a queue. To start a download, a download job is taken out and removed from the queue.

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

(Note the private readonly, which allows using this object as a synchronization object, as explained later.)

DownloadData is a simple type that contains all necessary information for each download job.

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

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


Now, how is this queue being filled? The code essentially exists already in your ExtractImages class. But instead of filling the two lists imagesSatelliteUrls and imagesRainUrls, the queue will be filled instead:

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


With the method for filling the queue in place, it just needs to be called and eventually the queue being processed. Processing of the download queue happens at two occasions/in two places: (1) another download item has to be started if one of the active downloads is finished, and (2) initially starting a number of downloads according to the number of available progress bars (=download slots).

The former will be covered a bit later. The latter, together with calling the AddImageDownloadsToQueue method will happen in 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;
                }
            }
    }
}

You will notice three things here. First, the usage of lock to synchronize access to the download queue, avoiding potential concurrent accesses and race conditions. Note, that using _downloadQueue as lock object is safe and only safe because it is declared as private readonly.

Second, a test if (pb.Tag == null) is done. The ProgressBar's Tag property is being (ab)used here as a flag to indicate whether the progress bar is already in use for a download or not. If its value is null, it means it is not in use. Any other value (non-null) means, it is currently used for a download. Assuming that you will likely queue up download items in several chunks, this approach ensures that always all available progress bars are being used, no matter when exactly new download items are added to the queue.

The third and most important is the method StartDownloadWithProgressBar. This method sets up a WebClient object and starts the download.

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

Although WebClient.DownloadFileAsync is being used for downloading files, one single WebClient object cannot handle multiple concurrent downloads. Hence each download needs its own WebClient instance. Two callbacks take care of the WebClient's DownloadFileCompleted and DownloadProgressChanged events. These callbacks will be covered a bit later.

An ActiveDownloadJob object is created which keeps track of the download job and its associated ProgressBar and WebClient (although the sample code outlined below doesn't require access to the WebClient again, it might be practical for future extensions to have a reference of the WebClient instance at hand).

The ActiveDownloadJob is assigned to the progress bar's Tag property, which is done to indicate that the progress bar is in use. Also, the ActiveDownloadJob object is passed as UserToken parameter to the DownloadFileAsync method. This is done so the callback methods will know which progress bar to manipulate when called by the (ongoing) download.

ActiveDownloadJob is a very simple class:

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


The callback method for a download being completed (DownloadCompletedCallback) has to take care about two things: checking for cancelled/failed downloads and start the next download job in the queue.

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

Note that access to the queue is synchronized by locking to the same synchronization object as happened in the method backgroundWorker1_DoWork. Furthermore, note how this method acquires the ActiveDownloadJob object instance of the download. Remember that the ActiveDownloadJob object was being passed as UserToken parameter to the DownloadFileAsync method? Here it surfaces again.

If there are no further download jobs in the queue, the Tag property of the progress bar is set to null to indicate the ProgressBar being free and available again, otherwise a new download job will be taken from the queue and started by calling StartDownloadWithProgressBar. StartDownloadWithProgressBar will set the progress bar's Tag property to the ActiveDownloadJob object of the new download.

No matter which case, it is always ensured that the ActiveDownloadJob object of the finished download is removed from the progress bar's Tag property, and thus it and the WebClient kept within will eventually be GC'ed.


Although we are almost finished, there is still some piece missing: The progress bar needs to be updated during the download. This happens in the callback method for the download progress:

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 obtains the ProgressBar object belonging to the download in the similar manner as DownloadCompletedCallback does.


If you need to know when all downloads are finished, here is a hint: If the queue is empty and no downloads are active, then all downloads have been finished (or, there were no downloads to begin with...)

于 2013-10-29T02:03:33.397 に答える