zoukankan      html  css  js  c++  java
  • 一个简单的利用 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 <= 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

    谢谢浏览!

  • 相关阅读:
    Java日志框架
    分布式任务并发调度
    并发(三) CountDownLatch
    并发(二)CyclicBarrier
    并发(一) Semaphore
    MySql
    Hash
    由一个序列化框架的更换引发的问题
    navicat 12 激活
    Spring security
  • 原文地址:https://www.cnblogs.com/Music/p/WebClient-DownloadFileAsync4.html
Copyright © 2011-2022 走看看