代码改变世界

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

2018-09-13 18:48  音乐让我说  阅读(422)  评论(0编辑  收藏  举报

接上一篇,我们继续优化它。

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

 

谢谢浏览!