一个简单的利用 WebClient 异步下载的示例(五)(完结篇)
2018-09-20 11:52 音乐让我说 阅读(1471) 评论(0) 编辑 收藏 举报接着上一篇,我们继续来优化。我们的 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(); } }
谢谢浏览!
作者:音乐让我说(音乐让我说 - 博客园)
出处:http://music.cnblogs.com/
文章版权归本人所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。