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