接着上一篇,我們繼續來優化。我們的 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(); } }
謝謝瀏覽!