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

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

     示例源文件:下载

posted on 2012-07-24 22:38  Bean.Hsiang  阅读(635)  评论(1编辑  收藏  举报