zoukankan      html  css  js  c++  java
  • 基于任务并行库实现多线程下载示例

          任务并行库 (TPL) 是 .NET Framework 4 版的 System.Threading 和 System.Threading.Tasks 命名空间中的一组公共类型和 API。 TPL 的目的在于简化向应用程序中添加并行性和并发性的过程,从而提高开发人员的工作效率。TPL 会动态地按比例调节并发程度,以便最有效地使用所有可用的处理器。 此外,TPL 还处理工作分区、ThreadPool 上的线程调度、取消支持、状态管理以及其他低级别的细节操作。 通过使用 TPL,您可以在将精力集中于程序要完成的工作,同时最大程度地提高代码的性能。从 .NET Framework 4 开始,TPL 是编写多线程代码和并行代码的首选方法。

          本示例以下载chromium win32版本作为目标,将任务并行库运用其中。
          首先简单看一下界面,放置一个进度条用于显示下载进度,两个Label分别显示当前已下载文件大小和文件总大小。


          再来看一下请求辅助的代码。首先是http请求方法,一个用于请求整个页面,另一个用于请求指定内容范围。相信做过分段下载的朋友应该不会陌生。

    private HttpWebResponse Requst(string url, string param, long from, long to, HttpMethod method = HttpMethod.Get)
    {
        HttpWebRequest req = WebRequest.Create(url) as HttpWebRequest;
        req.UserAgent = USER_AGENT;
        req.Accept = ACCEPT;
        req.ContentType = CONTENT_TYPE;
        req.KeepAlive = true;
        req.Method = method.ToString();
        req.AllowAutoRedirect = true;
        req.AddRange(from, to);
        //req.Proxy = new WebProxy("127.0.0.1", 8888)
        return req.GetResponse() as HttpWebResponse;
    }
    
    private HttpWebResponse Requst(string url, string param, HttpMethod method = HttpMethod.Get)
    {
        HttpWebRequest req = WebRequest.Create(url) as HttpWebRequest;
        req.UserAgent = USER_AGENT;
        req.Accept = ACCEPT;
        req.ContentType = CONTENT_TYPE;
        req.KeepAlive = true;
        req.Method = method.ToString();
        req.AllowAutoRedirect = true;
        //req.Proxy = new WebProxy("127.0.0.1", 8888)
        return req.GetResponse() as HttpWebResponse;
    }

          其次来看一下使用任务对象完成下载的主要代码,逻辑顺序是这样:

          1.用一个简单的分段函数处理文件原始大小,得到若干分段对象
          2.循环生成任务对象,得到分段下载的http响应对象
          3.完成并行写文件,并更新界面显示

    IList<Range> list = InitRange((int)total, taskCount);
    IList<Task> tasks = new List<Task>();
    
    File.Delete(ZIP_FILE);
    
    foreach (var item in list)
    {
        var task = new Task((o) =>
        {
            var item2 = (Range)o;
            HttpWebResponse subRes = Requst(string.Format(CHROME_DOWNLOAD, version), string.Empty, item2.from, item2.to);
            using (var sw = subRes.GetResponseStream())
            {
                using (var fs = new FileStream(ZIP_FILE, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite))
                {
                    fs.Seek(item2.from, SeekOrigin.Begin);
                    int length = 0;
                    byte[] byt = new byte[1000];
                    while ((length = sw.Read(byt, 0, byt.Length)) > 0)
                    {
                        fs.Write(byt, 0, length);
                        Interlocked.Add(ref globalCurrent, length);
                        //globalCurrent += length;
                        Current.Text = string.Format("{0}M", (globalCurrent / 1024.00 / 1024.00).ToString("##0.00"));
                        DownLoadProgress.Value = (int)globalCurrent;
                    }
                }
            }
            subRes.Close();
        }, item);
        tasks.Add(task);
        task.Start();
    }

          请特别注意一下任务对象执行时传参的形式,之前因为参数传得不对导致下载功能错误

          最后介绍一下剩下的代码,另外启动一个任务等待下载线程的结束,弹出提示。

    var taskClose = new Task((o) =>
    {
        var t = (IList<Task>)o;
        Task.WaitAll(t.ToArray());
        if (globalCurrent == total)
        {
            MessageBox.Show("Download finished !", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        this.Close();
    }, tasks);
    taskClose.Start();

          这个示例还有许多不足,比如没有对http请求异常进行处理,没有对写文件异常进行处理并给出提示,界面更新是在非主线程中完成,虽然可运行但并不符合winform的要求,窗口关闭没有检查任务线程状态并中止。这些细节足够花点时间优化了。作为示例,仅突出了主要目标。

         给个窗口类文件全景

    View Code
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Text;
    using System.Windows.Forms;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace ChromiumHelper
    {
        enum HttpMethod
        {
            Post,
            Get
        }
    
        public partial class Form1 : Form
        {
            const string USER_AGENT = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1212.0 Safari/537.2";
            const string ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            const string ACCEPT_CHARSET = "GBK,utf-8;q=0.7,*;q=0.3";
            const string ACCEPT_ENCODING = "gzip,deflate,sdch";
            const string ACCEPT_LANGUAGE = "zh-CN,zh;q=0.8";
            const string CONTENT_TYPE = "application/x-www-form-urlencoded";
            const string CHROME_VERSION = "http://commondatastorage.googleapis.com/chromium-browser-snapshots/Win/LAST_CHANGE";
            const string CHROME_DIRECT = "http://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?path=Win/{0}/";
            const string CHROME_DOWNLOAD = "http://commondatastorage.googleapis.com/chromium-browser-snapshots/Win/{0}/chrome-win32.zip";
            const string ZIP_FILE = "chrome-win32.zip";
            long total = 0;//文件总长度
            int taskCount = 3;//线程数
            long globalCurrent = 0;//累积下载长度
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Form1_Shown(object sender, EventArgs e)
            {
                //取得最新的版本号
                HttpWebResponse res = Requst(CHROME_VERSION, string.Empty);
                string version = string.Empty;
                using (var sw = new StreamReader(res.GetResponseStream()))
                {
                    version = sw.ReadToEnd();
                }
                res.Close();
    
                //请求下载文件,初始化界面内容
                res = Requst(string.Format(CHROME_DOWNLOAD, version), string.Empty);
                total = res.ContentLength;
                res.Close();
    
                DownLoadProgress.Maximum = (int)total;
                DownLoadProgress.Minimum = 0;
    
                Total.Text = string.Format("{0}M", (total / 1024.00 / 1024.00).ToString("##0.00"));
    
                IList<Range> list = InitRange((int)total, taskCount);
                IList<Task> tasks = new List<Task>();
    
                File.Delete(ZIP_FILE);
    
                foreach (var item in list)
                {
                    var task = new Task((o) =>
                   {
                       var item2 = (Range)o;
                       HttpWebResponse subRes = Requst(string.Format(CHROME_DOWNLOAD, version), string.Empty, item2.from, item2.to);
                       using (var sw = subRes.GetResponseStream())
                       {
                           using (var fs = new FileStream(ZIP_FILE, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite))
                           {
                               fs.Seek(item2.from, SeekOrigin.Begin);
                               int length = 0;
                               byte[] byt = new byte[1000];
                               while ((length = sw.Read(byt, 0, byt.Length)) > 0)
                               {
                                   fs.Write(byt, 0, length);
                                   Interlocked.Add(ref globalCurrent, length);
                                   //globalCurrent += length;
                                   Current.Text = string.Format("{0}M", (globalCurrent / 1024.00 / 1024.00).ToString("##0.00"));
                                   DownLoadProgress.Value = (int)globalCurrent;
                               }
                           }
                       }
                       subRes.Close();
                   }, item);
                    tasks.Add(task);
                    task.Start();
                }
    
                //另起一个任务,等待下载任务结束后弹出提示并主动关闭应用
                var taskClose = new Task((o) =>
                {
                    var t = (IList<Task>)o;
                    Task.WaitAll(t.ToArray());
                    if (globalCurrent == total)
                    {
                        MessageBox.Show("Download finished !", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
                    this.Close();
                }, tasks);
                taskClose.Start();
            }
    
            //private void Update(object current)
            //{
            //    int c = (int)current;
            //    Current.Text = ((int)current).ToString();
            //    DownLoadProgress.Value = c;
            //}
    
            /// <summary>
            /// 根据下载线程数计算每个线程下载大小,获取分段集合
            /// 取近似值,先均分然后把多余的加到最后一个分段上
            /// </summary>
            /// <param name="total"></param>
            /// <param name="part"></param>
            /// <returns></returns>
            private IList<Range> InitRange(long total, int part)
            {
                IList<Range> list = new List<Range>();
                long max = total;
                while (max % part > 1000000) { max++; }
                long division = max / part;
                long last = -1;
                for (int i = 0; i < part; i++)
                {
                    Range range = new Range { from = last + 1, to = (i + 1) * division };
                    list.Add(range);
                    last = range.to;
                }
                Range r = list.Last<Range>();
                r.to = total;
                list.RemoveAt(list.Count - 1);
                list.Add(r);
                return list;
            }
    
            private HttpWebResponse Requst(string url, string param, long from, long to, HttpMethod method = HttpMethod.Get)
            {
                HttpWebRequest req = WebRequest.Create(url) as HttpWebRequest;
                req.UserAgent = USER_AGENT;
                req.Accept = ACCEPT;
                req.ContentType = CONTENT_TYPE;
                req.KeepAlive = true;
                req.Method = method.ToString();
                req.AllowAutoRedirect = true;
                req.AddRange(from, to);
                //req.Proxy = new WebProxy("127.0.0.1", 8888)
                return req.GetResponse() as HttpWebResponse;
            }
    
            private HttpWebResponse Requst(string url, string param, HttpMethod method = HttpMethod.Get)
            {
                HttpWebRequest req = WebRequest.Create(url) as HttpWebRequest;
                req.UserAgent = USER_AGENT;
                req.Accept = ACCEPT;
                req.ContentType = CONTENT_TYPE;
                req.KeepAlive = true;
                req.Method = method.ToString();
                req.AllowAutoRedirect = true;
                //req.Proxy = new WebProxy("127.0.0.1", 8888)
                return req.GetResponse() as HttpWebResponse;
            }
    
        }
    
        struct Range
        {
            public long from;
            public long to;
        }
    }

         示例源文件:下载

  • 相关阅读:
    《棉花帝国:一部资本主义全球史》笔记
    关于”空杯之心“的重新思考
    《光荣与梦想:19321972年美国叙事史》笔记
    《哥伦布大交换:1492年以后的生物影响和文化冲击》笔记
    《增长、短缺与效率》笔记
    《逃不开的经济周期:历史、理论与投资现实》笔记
    《经济学》笔记
    《就业、利息和货币通论》笔记
    PostgreSQL与MySQL比较
    让svn自动更新代码注释中的版本号
  • 原文地址:https://www.cnblogs.com/BeanHsiang/p/2607402.html
Copyright © 2011-2022 走看看