基于任务并行库实现多线程下载示例

      任务并行库 (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;

    }

}

     示例源文件:下载

你可能感兴趣的:(多线程下载)