2

我正在寻找最快和最可靠的方法来同时使用 C# 下载 1000 个远程网页(使用 HttpWebRequest),将它们写入单个本地文件并在所有文件下载后运行一些处理代码,同时充分利用并行性和非阻塞并发可用。

服务器是运行 Windows 2008 和 .NET 4.0 的四核 (vCPU) VPS(不能使用较新的 async/await 东西)。

你有什么建议?

更新:目前建议的选项有:反应式扩展 (Rx)、异步 CTP、TPL。

看起来 Async CTP 将是理想的方法,其次是 Rx 和 TPL。大伙怎么说?

4

5 回答 5

5

无论您最终使用哪种异步方法,请不要忘记您需要增加允许的最大连接数,因为默认值为2 per domain。因此,如果您对单个域进行大量调用,您的速率将受到限制。

您可以使用基本配置在独立(非 ASP.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,可以将其用作“通用”解决方案。

于 2012-07-23T16:18:11.700 回答
4

I would use Rx for that task.

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));

Or if you like to control the concurrency to process max 4 requests concurrently as svick suggested you could change it to this:

Observable.ForkJoin(
    webpages
        .Select(w => FetchWebPage(w))
        .Merge(4))
        .Subscribe(x => /*This runs when all webpages have been fetched*/  Console.WriteLine(x));   

You also neeed a helper method to transform from the regular async way to the Rx way

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;
    });
}
于 2012-07-23T11:53:06.760 回答
1

VS2010 SP1 可以使用Async CTP在.NET 4.0 上做async/ 。awaitVS2012 RC 可以使用 Async Targeting Pack 来做async/ awaiton .NET 4.0。

但如果您真的不想使用async/ await,您仍然可以使用任务和延续(任务并行库是 .NET 4.0 的一部分)。

于 2012-07-23T11:14:07.663 回答
1

我也有类似的需求,但对我来说,URL 数量超过 7,000 个(过去大约需要 25 - 28 分钟才能完成)。对于我的解决方案,我使用了 TPL。由于每个 URL 没有依赖关系,因此很容易将每个 URL 封装在一个对象中,将其放入一个集合中,然后将该集合传递给 Parallel.ForEach() 调用。

每次下载完成后,我们都会查看页面的内容,根据我们找到的内容,我们会将其发送出去以进行额外处理。

正如我所说,这过去需要半小时才能完成,但现在运行时间约为 4.5 分钟(我有双四核 Xeon 处理器 @ 3GHz、Windows 7 Ultimate 64 位版和 24 GB RAM .... 大量的电力正在被利用,而不是大部分被浪费掉)。

微软的 TPL 给我留下了深刻的印象,以至于我回到了我的大部分遗留项目/代码并重构了设计以尽可能利用 TPL,并且我总是对我编写的任何新代码进行“TPL 处理”(它是如果您在循环迭代之间有任何类型的依赖关系,则并非总是可能的)。

于 2012-07-23T14:33:47.647 回答
0

我最近使用 C# 5 的新异步功能和 WebClent 而不是 HttpWebRequest 做了类似的事情。您可以使用 WebClient 获得一些不错的异步方法,例如 DownloadDataTaskAsync。

WebClient client = new WebClient();
byte[] data = await client.DownloadDataTaskAsync(url)
于 2012-07-23T11:27:12.197 回答