接着上一篇,我们继续来优化。我们的 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 = ; i <= ; i++)
{
ids.Add(i);
}
return ids;
} private void WhenAllDownloading()
{
this.listBoxLog.Items.Insert(, 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(, 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(, 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(, 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, );
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 = ; i <= ; i++)
{
ids.Add(i);
}
return ids;
} private void WhenAllDownloading()
{
this.listBoxLog.Items.Insert(, 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(, 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(, 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(, 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, );
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, );
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, )
{ } /// <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 <= )
{
SetIsCompletedTrue();
return;
}
while (OptionDataList.Count > && 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 > )
{
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();//表示已经完成
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) * / this.Maximum;
OnSetRealTimeValueForProgress(realTimeValue); //表示单个已经完成
}
catch (Exception)
{ }
}
StartAsyncCore();
}
}

谢谢浏览!

一个简单的利用 WebClient 异步下载的示例(五)(完结篇)的更多相关文章

  1. 一个简单的利用 WebClient 异步下载的示例(三)

    继续上一篇 一个简单的利用 WebClient 异步下载的示例(二) 后,继续优化它. 1. 直接贴代码了: DownloadEntry: public class DownloadEntry { p ...

  2. 一个简单的利用 WebClient 异步下载的示例(二)

    继上一篇 一个简单的利用 WebClient 异步下载的示例(一) 后,我想把核心的处理提取出来,成 SkyWebClient,如下: 1. SkyWebClient 该构造函数中 downloadC ...

  3. 一个简单的利用 WebClient 异步下载的示例(一)

    继上一篇文章 一个简单的利用 HttpClient 异步下载的示例 ,我们知道不管是 HttpClient,还算 WebClient,都不建议每次调用都 new HttpClient,或 new We ...

  4. 一个简单的利用 WebClient 异步下载的示例(四)

    接上一篇,我们继续优化它. 1. DownloadEntry 类 public class DownloadEntry { public string Url { get; set; } public ...

  5. 一个简单的利用 HttpClient 异步下载的示例

    可能你还会喜欢 一个简单的利用 WebClient 异步下载的示例  ,且代码更加新. 1. 定义自己的 HttpClient 类. using System; using System.Collec ...

  6. VC6下OpenGL 开发环境的构建外加一个简单的二维网络棋盘绘制示例

    一.安装GLUT 工具包 GLUT 不是OpenGL 所必须的,但它会给我们的学习带来一定的方便,推荐安装. Windows 环境下的GLUT 本地下载地址:glut-install.zip(大小约为 ...

  7. 一个简单的AXIS远程调用Web Service示例

    我们通常都将编写好的Web Service发布在Tomcat或者其他应用服务器上,然后通过浏览器调用该Web Service,返回规范的XML文件.但是如果我们不通过浏览器调用,而是通过客户端程序调用 ...

  8. Java RMI 实现一个简单的GFS(谷歌文件系统)——背景与设计篇

    目录 背景 系统设计 1. 系统功能 2. Master组件 2.1 命名空间 2.2 心跳机制 2.3 故障恢复和容错机制 3. ChunkServer组件 3.1 本地存储 3.2 内存命中机制 ...

  9. 使用ssm(spring+springMVC+mybatis)创建一个简单的查询实例(三)(错误整理篇)

    使用ssm(spring+springMVC+mybatis)创建一个简单的查询实例(一) 使用ssm(spring+springMVC+mybatis)创建一个简单的查询实例(二) 以上两篇已经把流 ...

随机推荐

  1. 软件设计之基于Java的连连看小游戏(二)——游戏基础界面的制作及事件的添加

    上次完成到游戏首页的制作,今天完成了游戏基础界面的制作以及事件的简单添加.由于功能尚未完全实现,因此游戏界面的菜单列表只是简单地添加了一下,其余菜单列表以及倒计时等在后续的制作中逐一完善. 1.首先在 ...

  2. Java 小游戏 - 井字棋 v1.0 (初步完成) (2018.4.16更新)

      井字棋游戏初步完成 实现功能:输入位置数据->打印棋盘->判断是否胜利->继续游戏/退出游戏 缺点:没有清屏函数   判断胜利方法太过无脑    package MYGAME; ...

  3. Dynamics 365中计算字段与Now进行计算实体导入报错:You can't use Now(), which is of type DateTime, with the current function.

    微软动态CRM专家罗勇 ,回复338或者20190521可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me. 计算字段是从Dynamics CRM 2015 SP1版本开始推 ...

  4. hadoop mapreduce求解有序TopN(高效模式)

    1.在map阶段对数据先求解改分片的topN,到reduce阶段再合并求解一次,求解过程利用TreeMap的排序特性,不用自己写算法. 2.样板数据,类似如下 1 13682846555 192.16 ...

  5. Linux—服务器SSL/TLS快速检测工具(TLSSLed)

    一.下载TLSSLed [root@localhost ~]# yum install tlssled 二.服务器SSL/TLS快速检测工具TLSSLed 现在SSL和TLS被广泛应用服务器的数据加密 ...

  6. (七)Amazon Lightsail 部署LAMP应用程序之清除已安装服务

    1.删除Lightsail实例 2.删除MySQL数据库 3.删除Amazon RDS数据库 4.删除负载均衡器 5.删除EC2实例 6.删除EC2安全组 7.删除快照

  7. MNIST手写数字识别进阶:多层神经网络及应用(1)

    # 一.载入数据 import tensorflow as tf import numpy as np #导入tensorflow提供的读取MNIST的模块 import tensorflow.exa ...

  8. IntelliJ IDEA 版本控制 GIT(四)

    1. 从GitLab或GitGitHub中检出项目 VCS - Checkout from Version Control - Git 2. 更新项目 第一种:更新当前窗口下的整个项目 第二种:更新相 ...

  9. LG4341/BZOJ2251 「BJWC2010」外星联络 Trie

    问题描述 LG4341 BZOJ2251 BZOJ需要权限号 题解 字符串的性质:一个字符串\(s\)所有的字串,等于\(s\)所有后缀的前缀. 枚举这个字符串的每一个后缀,将其插入一个\(\math ...

  10. aliyun-OSS断点续传

    阿里云OSS断点续传(Java版本) 在工作中发现开发的某项功能在用户网络环境差的时候部分图片无法显示,通过Review代码之后发现原来是图片上传到了国外的亚马逊服务器上,经过讨论决定将图片上传到国内 ...