Effective C# 学习笔记(三十六) 理解PLINQ对IO上瓶颈的处理方式
2011-07-23 21:04 小郝(Kaibo Hao) 阅读(542) 评论(1) 编辑 收藏 举报IO操作一般会成为程序性能的瓶颈,我们可以用并行的方式来提高该方面的操作效率。
首先看一个例子,下面是一个从地址列表中下载数据的IO操作循环代码片段。
//顺序执行IO操作
foreach (var url in urls)
{
var result = new WebClient().DownloadData(url);
UseResult(result);
}
使用Parallel.ForEach()
您可以使用Parallel.ForEach()方法来并行处理上面的IO操作。该方法会动态分配创建线程,在IO操作阻塞线程运行时,它会创建更多线程来处理IO操作,而当过多的线程处于活跃状态时,它又会自动地减少线程,来减少更多的线程间调度的工作。
//Parallel.ForEach()方法并行操作IO
Parallel.ForEach(urls, url =>
{
var result = new WebClient().DownloadData(url);
UseResult(result);
});
使用PLINQ的AsParallel()方法
您还可以使用PLINQ的AsParallel()方法来并行处理IO操作。这种操作创建固定个数的线程来处理并发,您可以使用WithDegreeOfParallelism() 来控制线程的个数。
//使用PLINQ并行处理IO
var results = from url in urls.AsParallel()
select new WebClient().DownloadData(url);
results.ForAll(result => UseResult(result));
其实以上的两种并行处理方式并不是真正意义上的并行操作,其只对运行的结果的处理并没有做到真正的异步并行处理。继续往下看…
下面的代码通过ParallellIO类的RunAsync方法来抽象异步IO处理的起始、结束回调的方法,以实现对Url的资源的异步操作。
/// <summary>
/// 下载资源类
/// </summary>
public class DownloadResource
{
/// <summary>
/// 下载地址
/// </summary>
public string URL { get; set; }
/// <summary>
/// 下载内容
/// </summary>
public byte[] Content { get; set; }
}
static class ParallelIO
{
/// <summary>
/// 并行运行
/// </summary>
/// <typeparam name="T">枚举集合数据项类型</typeparam>
/// <typeparam name="TResult">任务线程的返回值</typeparam>
/// <param name="taskParms">枚举结合对象</param>
/// <param name="taskStarter">起始处理方法</param>
/// <param name="taskFinisher">终止处理方法</param>
public static void RunAsync<T, TResult>(
this IEnumerable<T> taskParms,
Func<T, Task<TResult>> taskStarter,
Action<Task<TResult>> taskFinisher)
{
taskParms.Select(parm => taskStarter(parm)).
AsParallel().
ForAll(t => t.ContinueWith(t2 => taskFinisher(t2)));
}
//
/// <summary>
/// 处理完成时调用的handler
/// </summary>
/// <param name="downLoadResourceTask">下载资源的线程</param>
public static void finishDownload(Task<DownloadResource> downLoadResourceTask)
{
byte[] downloadedContent = downLoadResourceTask.Result.Content;
Console.WriteLine("Read {0} bytes from {1}",
downloadedContent.Length.ToString(),
downLoadResourceTask.Result.URL);
}
/// <summary>
/// 处理开始时调用的hander
/// </summary>
/// <param name="url">下载地址</param>
/// <returns>处理下载的任务对象</returns>
public static Task<DownloadResource> startDownload(string url)
{
var tcs = new TaskCompletionSource<DownloadResource>();
var wc = new WebClient();
wc.DownloadDataCompleted += (sender, e) =>
{
if (e.UserState == tcs)
{
if (e.Cancelled)
tcs.TrySetCanceled();
else if (e.Error != null)
tcs.TrySetException(e.Error);
else
tcs.TrySetResult(new DownloadResource()
{
Content = e.Result,
URL = url
});
}
};
wc.DownloadDataAsync(new Uri(url), tcs); //webClient使用了EAP异步处理下载
return tcs.Task;
}
}
Console.WriteLine("---------------ParallelIO Begin--------------");
string[] urls = { "http://www.google.com", "http://www.bing.com" ,"http://www.baidu.com"};
ParallelIO.RunAsync<string, DownloadResource>(urls, ParallelIO.startDownload, ParallelIO.finishDownload);
Console.WriteLine("---------------ParallelIO End--------------");
WebClient 使用了Event-based Asynchronous Pattern (EAP)(基于事件的异步模式)。也就是说你需要先注册一个handler到一个事件上,然后在一个异步操作完成后,这个事件会激活调用你的方法。 当下载事件开始时,startDownLoad()将任务的完成Task对象保存在TaskCompletionSource中,下载也就是开始了。而startDownload()方法将在task完成时返回Task对象。当下载完成时,触发DownloadDataCompleted事件,而事件handler会为TaskCompletionSoure设置完成状态。标识嵌入的任务已完成。
除了以上的EAP处理机制外,.NET 库还提供了 Asynchronous Programming Model (APM) pattern来处理其他的异步操作。在这种模式下,你先调用 Begin操作名()方法,进行起始操作,该方法返回 IAsyncResult类型的对象。当操作完成时,你调用End操作名()方法,传入IAsyncResult对象,处理完成时的逻辑。你可以使用Task<TResult> .Factory.FromAsync()方法来实现以上模式的调用。
出处:http://www.cnblogs.com/haokaibo/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。