代码改变世界

一个简单的利用 WebClient 异步下载的示例(五)(完结篇)

2018-09-20 11:52  音乐让我说  阅读(1485)  评论(0编辑  收藏  举报

接着上一篇,我们继续来优化。我们的 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 = 1; i <= 32; 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(WhenSingleDownloadEventArgs eventArg)
        {
            this.listBoxLog.Items.Insert(0, 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(0, 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(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));
            }
            // 方案4(同步下载,需要启动另外一个线程)
            ThreadPool.QueueUserWorkItem(new WaitCallback(RunByWebClientCore), downloadConfigs);
        }

        private void RunByWebClientCore(object state)
        {
            List<DownloadEntry> downloadConfigs = (List<DownloadEntry>)state;
            SkyParallelWebClient skyParallelWebClient = new SkyParallelWebClient(downloadConfigs, 10);
            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 = 1; i <= 32; 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(WhenSingleDownloadEventArgs eventArg)
        {
            this.listBoxLog.Items.Insert(0, 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(0, 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(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.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, 10);
            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, 20)
        {

        }

        /// <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 <= 0)
            {
                SetIsCompletedTrue();
                return;
            }
            while (OptionDataList.Count > 0 && 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 > 0)
            {
                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(100);//表示已经完成
                        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) * 100 / this.Maximum;
                    OnSetRealTimeValueForProgress(realTimeValue);   //表示单个已经完成
                }
                catch (Exception)
                {

                }
            }
            StartAsyncCore();
        }
    }

 

谢谢浏览!