一个简单的利用 WebClient 异步下载的示例(四)
接上一篇,我们继续优化它。
1. DownloadEntry 类
public class DownloadEntry
{
public string Url { get; set; } public string Path { get; set; } /// <summary>
/// 当前处理的数据
/// </summary>
public object Data { get; set; } public DownloadEntry(string url, string savedPath)
: this(url, savedPath, null)
{ } public DownloadEntry(string url, string savedPath, object data)
{
Url = url;
Path = savedPath;
Data = data;
}
}
2. 增加事件
/// <summary>
/// 当单个下载前的事件处理
/// </summary>
/// <param name="current">当前处理的数据,有可能为 NULL,请注意判断</param>
public delegate void WhenSingleDownloadingEventHandler(DownloadEntry current); /// <summary>
/// 当全部下载完毕后的事件处理
/// </summary>
public delegate void WhenAllDownloadedEventHandler(); /// <summary>
/// 当下载错误时
/// </summary>
/// <param name="ex"></param>
public delegate void WhenDownloadingErrorEventHandler(Exception ex);
3. 提取出 SkyWebClient 的基类
/// <summary>
/// SkyWebClient 的基类
/// </summary>
public class SkyWebClientBase : INotifyPropertyChanged
{
#region 字段、属性 public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
} /// <summary>
/// 当单个下载前的事件处理
/// </summary>
public event WhenSingleDownloadingEventHandler WhenSingleDownloading;
protected virtual void OnWhenSingleDownloading(DownloadEntry next)
{
if (WhenSingleDownloading != null)
{
WhenSingleDownloading(next);
}
} /// <summary>
/// 当全部下载完毕后的事件处理
/// </summary>
public event WhenAllDownloadedEventHandler WhenAllDownloaded;
protected virtual void OnWhenAllDownloaded()
{
if (WhenAllDownloaded != null)
{
WhenAllDownloaded();
}
} /// <summary>
/// 当全部下载完毕后的事件处理
/// </summary>
public event WhenDownloadingErrorEventHandler WhenDownloadingError;
protected virtual void OnWhenDownloadingError(Exception ex)
{
if (WhenDownloadingError != null)
{
WhenDownloadingError(ex);
}
} bool _canChange = true;
public bool CanChange
{
get
{
return _canChange;
}
set
{
_canChange = value;
OnPropertyChanged("CanChange");
}
} #endregion
}
4. SkyParallelWebClient
/// <summary>
/// 并行的 WebClient
/// </summary>
public class SkyParallelWebClient : SkyWebClientBase
{
ConcurrentQueue<DownloadEntry> OptionDataList = new ConcurrentQueue<DownloadEntry>(); //比如说:有 500 个元素 ConcurrentQueue<Task> ProcessingTasks = new ConcurrentQueue<Task>(); //当前运行中的 public int ParallelCount { get; set; } private bool IsCompleted { get; set; } private static object lockObj = new object(); /// <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);
}
} /// <summary>
/// 启动(备注:由于内部采用异步下载,所以方法不用加 Try 和返回值)
/// </summary>
public void Start()
{
System.Net.ServicePointManager.DefaultConnectionLimit = int.MaxValue;
StartCore();
} protected void StartCore()
{
if (OptionDataList.Count <= )
{
if (!IsCompleted)
{
lock (lockObj)
{
if (!IsCompleted)
{
OnWhenAllDownloaded();
IsCompleted = true;
}
}
}
return;
}
while (OptionDataList.Count > && ProcessingTasks.Count <= ParallelCount)
{
DownloadEntry downloadEntry;
if (!OptionDataList.TryDequeue(out downloadEntry))
{
break;
}
var task = DownloadFileAsync(downloadEntry);
ProcessingTasks.Enqueue(task);
OnWhenSingleDownloading(downloadEntry);
}
} private Task DownloadFileAsync(DownloadEntry downloadEntry)
{
using (WebClient webClient = new WebClient())
{
//set this to null if there is no proxy
webClient.Proxy = null;
webClient.DownloadFileCompleted += WebClient_DownloadFileCompleted;
return webClient.DownloadFileTaskAsync(new Uri(downloadEntry.Url), downloadEntry.Path);
}
} private void WebClient_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
Task task;
ProcessingTasks.TryDequeue(out task);
StartCore();
}
}
5. TaskDemo101
public static class TaskDemo101
{
public static string GetRandomUrl(Random rd)
{
string url1 = "http://www.xxx.me/Uploads/image/20130129/2013012920080761761.jpg";
string url2 = "http://www.xxx.me/Uploads/image/20121222/20121222230686278627.jpg";
string url3 = "http://www.xxx.me/Uploads/image/20120606/20120606222018461846.jpg";
string url4 = "http://www.xxx.me/Uploads/image/20121205/20121205224383848384.jpg";
string url5 = "http://www.xxx.me/Uploads/image/20121205/20121205224251845184.jpg"; string resultUrl;
int randomNum = rd.Next(, );
switch (randomNum)
{
case : resultUrl = url1; break;
case : resultUrl = url2; break;
case : resultUrl = url3; break;
case : resultUrl = url4; break;
case : resultUrl = url5; break;
default: throw new Exception("");
}
return resultUrl;
} public static string GetSavedFileFullName()
{
string targetFolderDestination = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "downloads\\images\\");
try
{
Directory.CreateDirectory(targetFolderDestination);
}
catch (Exception)
{
Console.WriteLine("创建文件夹失败!");
}
string targetFileDestination = Path.Combine(targetFolderDestination, string.Format("img_{0}{1}.png", DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"), Guid.NewGuid().ToString()));
return targetFileDestination;
} public static async Task<bool> RunByHttpClient(SkyHttpClient skyHttpClient, int id)
{
var task = skyHttpClient.DownloadImage(GetRandomUrl(new Random()));
return await task.ContinueWith<bool>(t => {
File.WriteAllBytes(GetSavedFileFullName(), t.Result);
return true;
});
} public static void RunByWebClient(WebClient webClient, int id)
{
webClient.DownloadFileAsync(new Uri(GetRandomUrl(new Random())), GetSavedFileFullName());
}
}
6. Form1
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(int id)
{
this.listBoxLog.Items.Insert(, string.Format("当前时间:{0},编号 {1} 准备开始下载...", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), id));
} 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.WhenAllDownloaded += SkyWebClient_WhenAllDownloaded;
skyParallelWebClient.WhenSingleDownloading += SkyWebClient_WhenSingleDownloading;
skyParallelWebClient.WhenDownloadingError += SkyWebClient_WhenDownloadingError;
skyParallelWebClient.Start();
} private void SkyWebClient_WhenDownloadingError(Exception ex)
{
MessageBox.Show("下载时出现错误: " + ex.Message);
} private void SkyWebClient_WhenSingleDownloading(DownloadEntry current)
{
WhenSingleDownloading((int)current.Data);
} private void SkyWebClient_WhenAllDownloaded()
{
btnRunByWebClient.Text = "用 WebClient 开始下载";
WhenAllDownloaded();
} }
7. 运行截图:
如图:


8. 总结
还算比较完美,唯独 下载完毕后,可能会多次调用事件,需要优化。
下载:https://files.cnblogs.com/files/Music/SkyParallelWebClient_v2018-09-18.rar
谢谢浏览!
一个简单的利用 WebClient 异步下载的示例(四)的更多相关文章
- 一个简单的利用 WebClient 异步下载的示例(三)
继续上一篇 一个简单的利用 WebClient 异步下载的示例(二) 后,继续优化它. 1. 直接贴代码了: DownloadEntry: public class DownloadEntry { p ...
- 一个简单的利用 WebClient 异步下载的示例(二)
继上一篇 一个简单的利用 WebClient 异步下载的示例(一) 后,我想把核心的处理提取出来,成 SkyWebClient,如下: 1. SkyWebClient 该构造函数中 downloadC ...
- 一个简单的利用 WebClient 异步下载的示例(一)
继上一篇文章 一个简单的利用 HttpClient 异步下载的示例 ,我们知道不管是 HttpClient,还算 WebClient,都不建议每次调用都 new HttpClient,或 new We ...
- 一个简单的利用 WebClient 异步下载的示例(五)(完结篇)
接着上一篇,我们继续来优化.我们的 SkyParallelWebClient 可否支持切换“同步下载模式”和“异步下载模式”呢,好处是大量的代码不用改,只需要调用 skyParallelWebClie ...
- 一个简单的利用 HttpClient 异步下载的示例
可能你还会喜欢 一个简单的利用 WebClient 异步下载的示例 ,且代码更加新. 1. 定义自己的 HttpClient 类. using System; using System.Collec ...
- VC6下OpenGL 开发环境的构建外加一个简单的二维网络棋盘绘制示例
一.安装GLUT 工具包 GLUT 不是OpenGL 所必须的,但它会给我们的学习带来一定的方便,推荐安装. Windows 环境下的GLUT 本地下载地址:glut-install.zip(大小约为 ...
- [.NET] 一步步打造一个简单的 MVC 电商网站 - BooksStore(四)
一步步打造一个简单的 MVC 电商网站 - BooksStore(四) 本系列的 GitHub地址:https://github.com/liqingwen2015/Wen.BooksStore &l ...
- 一个简单的AXIS远程调用Web Service示例
我们通常都将编写好的Web Service发布在Tomcat或者其他应用服务器上,然后通过浏览器调用该Web Service,返回规范的XML文件.但是如果我们不通过浏览器调用,而是通过客户端程序调用 ...
- [c#]WebClient异步下载文件并显示进度
摘要 在项目开发中经常会用到下载文件,这里使用winform实现了一个带进度条的例子. 一个例子 using System; using System.Collections.Generic; usi ...
随机推荐
- wpf 两个自定义控件
wpf 两个自定义控件 一个是IP控件,一个滑动条.先看下效果图 IPControl 1.实际工作中有时需要设置IP信息,就想着做一个ip控件.效果没有window自带的好,需要通过tab切换.但也能 ...
- Python 自定义元类的两种写法
有关元类是什么大家自己搜索了解,我这里写一下实现元类的两种写法 # 自定义元类 #继承type class LowercaseMeta(type): ''' 修改类的属性名称为小写的元类 ''' # ...
- PlayJava Day002
今日所学: /* 2019.08.19开始学习,此为补档. */ 流程控制 条件: 一重用if 二重用if ... else 三重用if ... else if ... else 多重用switch ...
- centOS服务器安装mongodb
1.为服务器添加mongodb的包管理工具,这就相当于在windows中安装npm,以便能用npm安装各种依赖.添加了这个包管理工具,才能在后面对mongodb做一系列操作. touch /etc/y ...
- Thread之模板模式
我们知道,在实际使用线程的时候,真正的执行逻辑都是写在run方法里面,run方法是线程的执行单元,如果我们直接使用Thread类实现多线程,那么run方法本身就是一个空的实现,如下: /** * If ...
- JS是解释型还是编译型语言?
解释型和编译型语言 解释型语言 解释型语言是对代码进行一句一句的直接运行,在程序运行期间,使用解释器动态将代码解释为机器码,再运行. 编译型语言 编译型语言是需要使用编译器先对代码进行编译为机器码,再 ...
- Swift 字典模型互转总结
现在很多iOS项目的开发开始转向Swift语言. 相信 Swift语言很快会成为iOS工程师 必备技能. 字典转模型, 模型转转字典在开发过程中扮演非常重要的角色. 今天就和大家分享一下使用Swift ...
- [转][读书笔记]深入理解java虚拟机
原文地址:http://blog.csdn.net/hanekawa/article/details/51972259 第二章 Java内存区域与内存溢出异常 一,运行时数据区域: 1. ...
- 离线安装python第三方库的实用方法:解决公司内网,服务器/电脑不能上网却需要安装python三方库问题(上:Windows环境中)
问题描述: 公司的windows电脑是内网,今天需要安装一个Twisted库,用过的应该都晓得,很常见的异步库,但是仅仅依靠Pypi上下载下来的离线.whl安装包是不行的,linux服务器都是远程连接 ...
- MySQL 主从复制开启 GTID
GTID (Golobal Transaction ID) 是对于一个已提交事务的唯一编号,并且是一个全局(主从复制)唯一的编号. GTID 复制和传统复制的区别:在启动主从复制时,不需要指定 bin ...