我正在寻找最快,最可靠的方法来同时使用C#下载1000个远程网页(使用HttpWebRequest),将它们写入单个本地文件并在下载所有文件后运行一些处理代码,同时充分利用可用的并行性和非阻塞并发性。
服务器是运行Windows 2008和.NET 4.0的四核(vCPU)VPS(不能使用较新的async / await东西)。
你有什么建议?
更新:目前提出的选项包括:Reactive Extensions(Rx),Async CTP,TPL。
看起来Async CTP是理想的做法,其次是Rx和TPL。怎么说男人?
答案 0 :(得分:4)
我会使用Rx完成该任务。
string[] webpages = { "http://www.google.com", "http://www.spiegel.de"};
webpages
.Select(w => FetchWebPage(w))
.ForkJoin()
.Subscribe(x => /*This runs when all webpages have been fetched*/ Console.WriteLine(x));
或者,如果您希望控制并发性以同时处理最多4个请求,因为svick建议您可以将其更改为:
Observable.ForkJoin(
webpages
.Select(w => FetchWebPage(w))
.Merge(4))
.Subscribe(x => /*This runs when all webpages have been fetched*/ Console.WriteLine(x));
你还需要一个辅助方法来从常规异步方式转换为Rx方式
public static IObservable<string> FetchWebPage(string address)
{
var client = new WebClient();
return Observable.Create<string>(observer =>
{
DownloadStringCompletedEventHandler handler = (sender, args) =>
{
if (args.Cancelled)
observer.OnCompleted();
else if(args.Error != null)
observer.OnError(args.Error);
else
{
observer.OnNext(args.Result);
observer.OnCompleted();
}
};
client.DownloadStringCompleted += handler;
try
{
client.DownloadStringAsync(new Uri(address));
}
catch (Exception ex)
{
observer.OnError(ex);
}
return () => client.DownloadStringCompleted -= handler;
});
}
答案 1 :(得分:4)
无论您最终使用哪种异步方法,都不要忘记您需要增加允许的最大连接数,因为默认情况下每个域 2 。因此,如果您针对单个域进行大量调用,则您的速率将受到限制。
您可以使用基本配置在独立(nonASP.NET)应用中修复此问题:
<system.net>
<connectionManagement>
<add address="*" maxconnections="200" />
</connectionManagement>
</system.net>
但是,如果你在ASP.NET中,这将无法按预期工作,因为默认的<processModel autoConfig="true" ...>
属性将导致它自动配置为每个核心12个,虽然优于2个,但仍可能不适合您的需求。那么你将不得不在像Application_Start这样的方法中使用基于代码的方法:
ServicePointManager.DefaultConnectionLimit = 200;
注意:这种基于代码的方法同样适用于非ASP.NET应用程序,因此如果您想避免使用.config,可以将其用作“通用”解决方案。
答案 2 :(得分:1)
VS2010 SP1可以使用Async CTP在.NET 4.0上执行async
/ await
。
VS2012 RC可以使用Async Targeting Pack在.NET 4.0上执行async
/ await
。
但如果你真的不想使用async
/ await
,你仍然可以使用任务和延续(任务并行库是.NET 4.0的一部分)。
答案 3 :(得分:1)
我有类似的需求,但对我来说,URL计数超过7,000(过去需要大约25 - 28分钟才能完成)。对于我的解决方案,我使用了TPL。由于每个URL都没有依赖关系,因此很容易将每个URL封装在一个对象中,将其放入一个集合中,并将该集合传递给一个Parallel.ForEach()调用。
每次下载完成后,我们会看一下页面内容,根据我们发现的内容,我们将其发送给其他处理。
正如我所说,过去需要花费半个多小时的时间来完成,但现在大约需要4.5分钟(我有双核四核至强处理器@ 3GHz,Windows 7旗舰版64位版本和24 GB的RAM ......现在正在使用大量的电力,而不是浪费掉。)
我对微软的TPL印象深刻,我已经回到了大部分遗留项目/代码并重构了设计以尽可能利用TPL,我总是在任何新代码上给予“TPL处理”写(如果你在循环迭代之间有任何类型的依赖,那么它并不总是可能的。)
答案 4 :(得分:0)
我最近使用C#5的新异步功能和WebClent而不是HttpWebRequest做了类似的事情。您可以使用WebClient获得一些不错的异步方法,例如DownloadDataTaskAsync。
WebClient client = new WebClient();
byte[] data = await client.DownloadDataTaskAsync(url)