一個簡單的利用 WebClient 異步下載的示例(五)(完結篇)


接着上一篇,我們繼續來優化。我們的 SkyParallelWebClient 可否支持切換“同步下載模式”和“異步下載模式”呢,好處是大量的代碼不用改,只需要調用 skyParallelWebClient.StartAsync() 就開始異步下載,而改為 skyParallelWebClient.StartSync(); 就同步下載。如圖:

同步下載:

異步下載:

 

1. 同步下載模式

直接貼代碼了:

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

        private List<int> GetDownloadIds()
        {
            List<int> ids = new List<int>();
            for (int i = 1; i <= 32; i++)
            {
                ids.Add(i);
            }
            return ids;
        }

        private void WhenAllDownloading()
        {
            this.listBoxLog.Items.Insert(0, string.Format("當前時間:{0},准備開始下載...", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
            //禁用按鈕
            EnableOrDisableButtons(false);
        }

        private void EnableOrDisableButtons(bool enabled)
        {
            this.btnRunByHttpClient.Enabled = enabled;
            this.btnRunByWebClient.Enabled = enabled;
        }

        private void WhenSingleDownloading(WhenSingleDownloadEventArgs eventArg)
        {
            this.listBoxLog.Items.Insert(0, string.Format("當前時間:{0},剩余 {1} 個。編號 {2} 准備開始下載...", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), eventArg.RemainingQueueCount, eventArg.CurrentDownloadEntry.Data));
        }

        private void WhenSingleDownloaded(WhenSingleDownloadEventArgs eventArg)
        {
            this.listBoxLog.Items.Insert(0, string.Format("當前時間:{0},剩余 {1} 個。編號 {2} 下載完畢...", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), eventArg.RemainingQueueCount, eventArg.CurrentDownloadEntry.Data));
        }

        private void WhenAllDownloaded()
        {
            this.listBoxLog.Items.Insert(0, string.Format("當前時間:{0},下載完畢!", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
            //啟用按鈕
            EnableOrDisableButtons(true);
        }

        private async void btnRunByHttpClient_Click(object sender, EventArgs e)
        {
            //SkyHttpClient skyHttpClient = new SkyHttpClient();
            //try
            //{
            //    WhenAllDownloading();
            //    foreach (var id in GetDownloadIds())
            //    {
            //        bool singleDownloadSuccess = await TaskDemo101.RunByHttpClient(skyHttpClient, id);
            //        WhenSingleDownloading(id);
            //    }
            //    WhenAllDownloaded();
            //}
            //catch (Exception ex)
            //{
            //    MessageBox.Show(ex.Message, "Download Error!");
            //}
        }

        private void btnRunByWebClient_Click(object sender, EventArgs e)
        {
            WhenAllDownloading();
            var ids = GetDownloadIds();
            List<DownloadEntry> downloadConfigs = new List<DownloadEntry>();
            Random rd = new Random();
            foreach (var id in ids)
            {
                downloadConfigs.Add(new DownloadEntry(TaskDemo101.GetRandomUrl(rd), TaskDemo101.GetSavedFileFullName(), id));
            }
            // 方案4(同步下載,需要啟動另外一個線程)
            ThreadPool.QueueUserWorkItem(new WaitCallback(RunByWebClientCore), downloadConfigs);
        }

        private void RunByWebClientCore(object state)
        {
            List<DownloadEntry> downloadConfigs = (List<DownloadEntry>)state;
            SkyParallelWebClient skyParallelWebClient = new SkyParallelWebClient(downloadConfigs, 10);
            skyParallelWebClient.SetMaximumForProgress += SkyParallelWebClient_SetMaximumForProgress;
            skyParallelWebClient.SetRealTimeValueForProgress += SkyParallelWebClient_SetRealTimeValueForProgress;
            skyParallelWebClient.WhenAllDownloaded += SkyWebClient_WhenAllDownloaded;
            skyParallelWebClient.WhenSingleDownloading += SkyWebClient_WhenSingleDownloading;
            skyParallelWebClient.WhenSingleDownloaded += SkyWebClient_WhenSingleDownloaded;
            skyParallelWebClient.WhenDownloadingError += SkyWebClient_WhenDownloadingError;
            skyParallelWebClient.StartSync();
        }

        private void SkyParallelWebClient_SetRealTimeValueForProgress(int realTimeValue)
        {
            this.progressBar1.Value = realTimeValue;
        }

        private void SkyParallelWebClient_SetMaximumForProgress(int maximum)
        {
            this.progressBar1.Maximum = maximum;
        }

        private void SkyWebClient_WhenDownloadingError(Exception ex)
        {
            MessageBox.Show("下載時出現錯誤: " + ex.Message);
        }

        private void SkyWebClient_WhenSingleDownloading(WhenSingleDownloadEventArgs eventArg)
        {
            WhenSingleDownloading(eventArg);
        }

        private void SkyWebClient_WhenSingleDownloaded(WhenSingleDownloadEventArgs eventArg)
        {
            WhenSingleDownloaded(eventArg);
        }

        private void SkyWebClient_WhenAllDownloaded()
        {
            btnRunByWebClient.Text = "用 WebClient 開始下載";
            WhenAllDownloaded();
        }

        
    }

運行截圖:

2. 異步下載模式

直接貼代碼了:

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

        private List<int> GetDownloadIds()
        {
            List<int> ids = new List<int>();
            for (int i = 1; i <= 32; i++)
            {
                ids.Add(i);
            }
            return ids;
        }

        private void WhenAllDownloading()
        {
            this.listBoxLog.Items.Insert(0, string.Format("當前時間:{0},准備開始下載...", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
            //禁用按鈕
            EnableOrDisableButtons(false);
        }

        private void EnableOrDisableButtons(bool enabled)
        {
            this.btnRunByHttpClient.Enabled = enabled;
            this.btnRunByWebClient.Enabled = enabled;
        }

        private void WhenSingleDownloading(WhenSingleDownloadEventArgs eventArg)
        {
            this.listBoxLog.Items.Insert(0, string.Format("當前時間:{0},剩余 {1} 個。編號 {2} 准備開始下載...", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), eventArg.RemainingQueueCount, eventArg.CurrentDownloadEntry.Data));
        }

        private void WhenSingleDownloaded(WhenSingleDownloadEventArgs eventArg)
        {
            this.listBoxLog.Items.Insert(0, string.Format("當前時間:{0},剩余 {1} 個。編號 {2} 下載完畢...", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), eventArg.RemainingQueueCount, eventArg.CurrentDownloadEntry.Data));
        }

        private void WhenAllDownloaded()
        {
            this.listBoxLog.Items.Insert(0, string.Format("當前時間:{0},下載完畢!", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
            //啟用按鈕
            EnableOrDisableButtons(true);
        }

        private async void btnRunByHttpClient_Click(object sender, EventArgs e)
        {
            //SkyHttpClient skyHttpClient = new SkyHttpClient();
            //try
            //{
            //    WhenAllDownloading();
            //    foreach (var id in GetDownloadIds())
            //    {
            //        bool singleDownloadSuccess = await TaskDemo101.RunByHttpClient(skyHttpClient, id);
            //        WhenSingleDownloading(id);
            //    }
            //    WhenAllDownloaded();
            //}
            //catch (Exception ex)
            //{
            //    MessageBox.Show(ex.Message, "Download Error!");
            //}
        }

        private void btnRunByWebClient_Click(object sender, EventArgs e)
        {
            WhenAllDownloading();
            var ids = GetDownloadIds();
            List<DownloadEntry> downloadConfigs = new List<DownloadEntry>();
            Random rd = new Random();
            foreach (var id in ids)
            {
                downloadConfigs.Add(new DownloadEntry(TaskDemo101.GetRandomUrl(rd), TaskDemo101.GetSavedFileFullName(), id));
            }
            //搜索: Parallel WebClient
            // 方案1
            //SkyWebClient skyWebClient = new SkyWebClient(downloadConfigs, this.progressBar1);
            //skyWebClient.WhenAllDownloaded += SkyWebClient_WhenAllDownloaded;
            //skyWebClient.WhenSingleDownloading += SkyWebClient_WhenSingleDownloading;
            //skyWebClient.WhenDownloadingError += SkyWebClient_WhenDownloadingError;
            //skyWebClient.Start();
            // 方案2(代碼已經調整,無法恢復)
            //ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncRunSkyParallelWebClient), downloadConfigs);
            // 方案3(異步下載,完美)
            SkyParallelWebClient skyParallelWebClient = new SkyParallelWebClient(downloadConfigs, 10);
            skyParallelWebClient.SetMaximumForProgress += SkyParallelWebClient_SetMaximumForProgress;
            skyParallelWebClient.SetRealTimeValueForProgress += SkyParallelWebClient_SetRealTimeValueForProgress;
            skyParallelWebClient.WhenAllDownloaded += SkyWebClient_WhenAllDownloaded;
            skyParallelWebClient.WhenSingleDownloading += SkyWebClient_WhenSingleDownloading;
            skyParallelWebClient.WhenSingleDownloaded += SkyWebClient_WhenSingleDownloaded;
            skyParallelWebClient.WhenDownloadingError += SkyWebClient_WhenDownloadingError;
            skyParallelWebClient.StartSync();
            // 方案4(同步下載,需要啟動另外一個線程)
            //ThreadPool.QueueUserWorkItem(new WaitCallback(RunByWebClientCore), downloadConfigs);
        }

        private void RunByWebClientCore(object state)
        {
            List<DownloadEntry> downloadConfigs = (List<DownloadEntry>)state;
            SkyParallelWebClient skyParallelWebClient = new SkyParallelWebClient(downloadConfigs, 10);
            skyParallelWebClient.SetMaximumForProgress += SkyParallelWebClient_SetMaximumForProgress;
            skyParallelWebClient.SetRealTimeValueForProgress += SkyParallelWebClient_SetRealTimeValueForProgress;
            skyParallelWebClient.WhenAllDownloaded += SkyWebClient_WhenAllDownloaded;
            skyParallelWebClient.WhenSingleDownloading += SkyWebClient_WhenSingleDownloading;
            skyParallelWebClient.WhenSingleDownloaded += SkyWebClient_WhenSingleDownloaded;
            skyParallelWebClient.WhenDownloadingError += SkyWebClient_WhenDownloadingError;
            skyParallelWebClient.StartSync();
        }

        private void SkyParallelWebClient_SetRealTimeValueForProgress(int realTimeValue)
        {
            this.progressBar1.Value = realTimeValue;
        }

        private void SkyParallelWebClient_SetMaximumForProgress(int maximum)
        {
            this.progressBar1.Maximum = maximum;
        }

        private void SkyWebClient_WhenDownloadingError(Exception ex)
        {
            MessageBox.Show("下載時出現錯誤: " + ex.Message);
        }

        private void SkyWebClient_WhenSingleDownloading(WhenSingleDownloadEventArgs eventArg)
        {
            WhenSingleDownloading(eventArg);
        }

        private void SkyWebClient_WhenSingleDownloaded(WhenSingleDownloadEventArgs eventArg)
        {
            WhenSingleDownloaded(eventArg);
        }

        private void SkyWebClient_WhenAllDownloaded()
        {
            btnRunByWebClient.Text = "用 WebClient 開始下載";
            WhenAllDownloaded();
        }
        
    }

 

運行截圖:

如上圖:當打印了“下載完畢”后,仍然打印了許多日志,這是因為異步下載的回調是不定時的。為了避免這種情況,建議 WhenSingleDownloading 事件和 WhenSingleDownloaded 事件二選一,因為實在沒有必要下載開始前和下載后都打印日志。我們再次修改代碼后,得到如下圖的日志。

日志是不是清晰了很多?一般下載完成,可以注冊完成事件,事件里面不用打印日志,而做一些比如“修改數據庫表記錄的狀態字段”等等。

 

3. 總結

SkyParallelWebClient 完整的代碼如下:

    /// <summary>
    /// 設置進度條的最大值的事件處理
    /// </summary>
    /// <param name="maximum"></param>
    public delegate void SetMaximumForProgressEventHandler(int maximum);

    /// <summary>
    /// 設置進度條的當前實時的值的事件處理
    /// </summary>
    /// <param name="maximum"></param>
    public delegate void SetRealTimeValueForProgressEventHandler(int realTimeValue);

    /// <summary>
    /// 並行的 WebClient
    /// </summary>
    public class SkyParallelWebClient : SkyWebClientBase
    {
        ConcurrentQueue<DownloadEntry> OptionDataList = new ConcurrentQueue<DownloadEntry>(); //比如說:有 500 個元素

        ConcurrentDictionary<WebClient, DownloadEntry> ProcessingTasks = new ConcurrentDictionary<WebClient, DownloadEntry>(); //當前運行中的

        public event SetMaximumForProgressEventHandler SetMaximumForProgress;
        protected virtual void OnSetMaximumForProgress(int maximum)
        {
            if (SetMaximumForProgress != null)
            {
                SetMaximumForProgress(maximum);
            }
        }

        public event SetRealTimeValueForProgressEventHandler SetRealTimeValueForProgress;
        protected virtual void OnSetRealTimeValueForProgress(int realTimeValue)
        {
            if (SetRealTimeValueForProgress != null)
            {
                SetRealTimeValueForProgress(realTimeValue);
            }
        }

        public int ParallelCount { get; }

        public bool IsCompleted { get; private set; }

        public int Maximum { get;}

        private static object lockObj = new object();

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="downloadConfigs">要下載的全部集合,比如 N 多要下載的,N無限制</param>
        public SkyParallelWebClient(IEnumerable<DownloadEntry> downloadConfigs)
            :this(downloadConfigs, 20)
        {

        }

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="downloadConfigs">要下載的全部集合,比如 N 多要下載的,N無限制</param>
        /// <param name="parallelCount">單位內,並行下載的個數。切忌:該數字不能過大,否則可能很多文件因為 WebClient 超時,而導致亂文件(即:文件不完整)一般推薦 20 個左右</param>
        public SkyParallelWebClient(IEnumerable<DownloadEntry> downloadConfigs, int parallelCount)
        {
            if (downloadConfigs == null)
            {
                throw new ArgumentNullException("downloadConfigs");
            }
            this.ParallelCount = parallelCount;
            foreach (var item in downloadConfigs)
            {
                OptionDataList.Enqueue(item);
            }
            this.Maximum = OptionDataList.Count;
            System.Net.ServicePointManager.DefaultConnectionLimit = int.MaxValue;
            OnSetMaximumForProgress(this.Maximum);
        }

        /// <summary>
        /// 異步啟動(備注:由於內部采用異步下載,所以方法不用加 Try 和返回值)
        /// </summary>
        public void StartAsync()
        {
            StartAsyncCore();
        }

        protected void StartAsyncCore()
        {
            if (OptionDataList.Count <= 0)
            {
                SetIsCompletedTrue();
                return;
            }
            while (OptionDataList.Count > 0 && ProcessingTasks.Count <= ParallelCount)
            {
                DownloadEntry downloadEntry;
                if (!OptionDataList.TryDequeue(out downloadEntry))
                {
                    break;
                }
                DownloadFileAsync(downloadEntry);
                OnWhenSingleDownloading(new WhenSingleDownloadEventArgs
                {
                    CurrentDownloadEntry = downloadEntry,
                    RemainingQueueCount = OptionDataList.Count
                });
            }
        }

        /// <summary>
        /// 同步啟動
        /// </summary>
        public void StartSync()
        {
            StartSyncCore();
        }

        /// <summary>
        /// 同步啟動
        /// </summary>
        public void StartSync(WebClient webClient)
        {
            StartSyncCore(webClient);
        }

        protected void StartSyncCore()
        {
            using (WebClient webClient = new WebClient())
            {
                StartSyncCore(webClient);
            }
        }

        protected void StartSyncCore(WebClient webClient)
        {
            while (OptionDataList.Count > 0)
            {
                DownloadEntry downloadEntry;
                if (!OptionDataList.TryDequeue(out downloadEntry))
                {
                    break;
                }
                OnWhenSingleDownloading(new WhenSingleDownloadEventArgs
                {
                    CurrentDownloadEntry = downloadEntry,
                    RemainingQueueCount = OptionDataList.Count
                });
                DownloadFile(downloadEntry, webClient);
                OnWhenSingleDownloaded(new WhenSingleDownloadEventArgs
                {
                    CurrentDownloadEntry = downloadEntry,
                    RemainingQueueCount = OptionDataList.Count
                });
            }
            SetIsCompletedTrue();
        }

        protected void SetIsCompletedTrue()
        {
            if (!IsCompleted)
            {
                lock (lockObj)
                {
                    if (!IsCompleted)
                    {
                        OnSetRealTimeValueForProgress(100);//表示已經完成
                        OnWhenAllDownloaded();
                        IsCompleted = true;
                    }
                }
            }
        }

        private void DownloadFileAsync(DownloadEntry downloadEntry)
        {
            using (WebClient webClient = new WebClient())
            {
                webClient.Proxy = null;
                webClient.DownloadFileCompleted += WebClient_DownloadFileCompleted;
                webClient.DownloadFileAsync(new Uri(downloadEntry.Url), downloadEntry.Path);
                ProcessingTasks.TryAdd(webClient, downloadEntry);
            }
        }

        private void DownloadFile(DownloadEntry downloadEntry)
        {
            using (WebClient webClient = new WebClient())
            {
                DownloadFile(downloadEntry, webClient);
            }
        }

        private void DownloadFile(DownloadEntry downloadEntry, WebClient webClient)
        {
            try
            {
                webClient.Proxy = null;
                webClient.DownloadFile(new Uri(downloadEntry.Url), downloadEntry.Path);
            }
            catch (Exception ex)
            {
                OnWhenDownloadingError(ex);
            }
        }

        private void WebClient_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            WebClient webClient = (WebClient)sender;
            DownloadEntry downloadEntry;
            if (ProcessingTasks.TryRemove(webClient, out downloadEntry))
            {
                OnWhenSingleDownloaded(new WhenSingleDownloadEventArgs
                {
                    CurrentDownloadEntry = downloadEntry,
                    RemainingQueueCount = OptionDataList.Count
                });
                try
                {
                    int realTimeValue = (this.Maximum - OptionDataList.Count) * 100 / this.Maximum;
                    OnSetRealTimeValueForProgress(realTimeValue);   //表示單個已經完成
                }
                catch (Exception)
                {

                }
            }
            StartAsyncCore();
        }
    }

 

謝謝瀏覽!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM