接上一篇,我们继续优化它。
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 <= 0) { if (!IsCompleted) { lock (lockObj) { if (!IsCompleted) { OnWhenAllDownloaded(); IsCompleted = true; } } } return; } while (OptionDataList.Count > 0 && 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(1, 6); switch (randomNum) { case 1: resultUrl = url1; break; case 2: resultUrl = url2; break; case 3: resultUrl = url3; break; case 4: resultUrl = url4; break; case 5: 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>(100); for (int i = 1; i <= 55; 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(int id) { this.listBoxLog.Items.Insert(0, string.Format("当前时间:{0},编号 {1} 准备开始下载...", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), id)); } 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.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
谢谢浏览!