接着上一篇,我们继续来优化。我们的 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. 315道Python常见面试题

    第一部分,Python基础篇 为什么学习Python? 通过什么途径学习的Python? Python和Java.PHP.C.C#.C++等其他语言的对比? 简述解释型和编译型编程语言? Python ...

  2. Docker基础概念与安装

    Docker是什么? Docker最初是dotCloud公司的创始人Solomon Hyks在法国期间发起的一个公司内部项目,它是基于dotCloud公司多年云服务技术的一次革新,并于2013年3月以 ...

  3. sftp-server 搭建编译

    下载开源代码 https://github.com/zwx230741/openssh-portable 编译 # autoconf # ./configure --prefix=xxx # make ...

  4. JavaScriptCore

    在移动的混合开发中经常用到OC与JS的交互,就涉及iOS中的JavaScriptCore类,下面终结如下 JavaScriptCore中的类 在项目中引入JavaScriptCore后,链到头文件中, ...

  5. Git:SSH、SSH与HTTP区别、git常用命令

    SSH1.首先需要检查你电脑是否已经有 SSH key cd ~/.sshls12.ssh表示ssh文件是个隐藏文件查看是否存在 id_rsa.pub 或 id_dsa.pub 文件,如果文件已经存在 ...

  6. 为什么 Redis 为什么如此受欢迎

    现在大多数开发人员都会听说过 Redis.Redis 是目前市场上最好的开源内存 NoSQL 数据库之一.它为前端以及后端服务(如键值查找,队列,哈希等)提供了非常多的帮助. 一.什么是 Redis? ...

  7. 当时学习《鸟哥的Linux私房菜-基础学习篇》记录的点

    1.当执行一个指令的时候,举例来说[ls],系统会依照PATH的设定去每个PATH定义的目录下搜寻文件名为ls的可执行文件,如果在PATH定义的目录中含有多个文件名为ls的可执行文件,那么先搜寻到的同 ...

  8. postman---postman自动发博客

    前面写了一篇如何通过Cookies值去登录博客园,今天我们来通过登录博客园之后,我们进行通过Postman自动写博客 自动写博客 1.打开Postman.填写博客园对应的Cookies: 2.抓取编写 ...

  9. nginx代理ambassador,再转到mlfow-tracking服务

    这个服务的代理,相对于服务网关来说,有些典型, 今天调通了,作个记录. 一,nginx配置 upstream ai_ambassador { ip_hash; server 1.2.3.4:30080 ...

  10. 2.Python爬虫入门_requests

    #2019-11-23 #requests的api使用非常简单 import requests import time if __name__=='__main__': # get请求 url_get ...