2

我正在尋找最快和最可靠的方法來下載1000個遠程網頁(使用HttpWebRequest的)同時使用C#,將它們寫入單個本地文件和運行一些處理代碼,一旦所有的文件都被下載同時充分利用並行性和非阻塞併發性。併發下載/處理在C#

服務器是一個四核心(vCPU的)VPS運行Windows 2008和.NET 4.0(不能用較新的異步/等待的東西)。

你有什麼建議?

更新:到目前爲止提出的選項是:反應性擴展(Rx),異步CTP,TPL。

貌似異步CTP會做到這一點的理想方式,其次是Rx和TPL。什麼說傢伙?

+0

WebClient的作品。 – 2012-07-23 10:56:23

+0

所有這些頁面是來自單個網站還是來自1000個不同的網站? – svick 2012-07-23 11:28:41

+0

@Ramhound我不認爲WebClient本地支持多線程。 – Nick 2012-07-23 11:40:11

回答

1

VS2010 SP1可以使用異步CTP對.NET 4.0做async/await。 VS2012 RC可以使用異步定位包到.NET 4.0做async/await

但是,如果你真的不想使用async/await,你仍然可以使用任務和延續(Task Parallel Library是.NET 4.0的一部分)。

0

我最近用C#5的新功能,異步和WebClent代替HttpWebRequest的類似的東西。您可以使用WebClient獲得一些不錯的異步方法,例如DownloadDataTaskAsync。

WebClient client = new WebClient(); 
byte[] data = await client.DownloadDataTaskAsync(url) 
+0

這可能是很好的知道,但它不回答這個問題。特別是因爲OP說他不能使用'async'''await'。 – svick 2012-07-23 11:29:37

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

也neeed一個輔助方法,從普通異步轉換方式到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; 
    }); 
} 
+0

你能修改這個來限制並行度嗎?因爲我不認爲同時啓動1000次下載是個好主意。 – svick 2012-07-23 12:32:50

+0

如果我沒有弄錯,Rx在內部使用TPL。它看起來像TPL提供了比Rx更直接的控制異步操作​​。 – Nick 2012-07-23 12:39:51

+0

@Nick:Rx使用自己的內部函數(調度程序等)。我不相信它是建立在TPL上的。 – 2012-07-23 12:45:55

1

我也有類似的需求,但對我來說URL計數超過7000(用於約需25 - 27分鐘完成)。對於我的解決方案,我使用了TPL。由於每個URL都沒有依賴關係,因此很容易將每個對象封裝在一個對象中,將其放入一個集合中,並將該集合傳遞給一個Parallel.ForEach()調用。

隨着每個下載完成,我們看看頁面的內容,並根據我們找到的內容,將其發送給其他處理。正如我所說過的,這個過程花費了半小時的時間完成,但現在運行時間約爲4.5分鐘(我有雙核四Xeon處理器@ 3GHz,Windows 7 Ultimate 64位版本,以及24 GB的RAM ......大量的功耗正在被利用,而不是大部分的浪費)。

微軟的TPL給我留下了深刻的印象,我回到了大部分我的遺留項目/代碼,並重新設計了可能的TPL優勢,並且我總是在任何新代碼上給出「TPL處理」我寫(如果在循環迭代之間有任何類型的依賴關係,這並不總是可能的)。

4

不管你最終使用,不要忘記,你需要增加允許作爲默認的最大連接該異步方法是2每個域。因此,如果您針對單個域名進行大量呼叫,則會受到此限制。

您可以使用基本配置在一個獨立的(non-ASP.NET)應用程序解決這個問題:

<system.net> 
    <connectionManagement> 
     <add address="*" maxconnections="200" /> 
    </connectionManagement> 
</system.net> 

不過,如果你在ASP.NET是因爲默認<processModel autoConfig="true" ...>如預期,這將不起作用屬性會導致它自動配置爲每個內核12個,雖然總數優於2個,但仍可能不適合您的需求。所以,那麼你將不得不使用基於代碼的辦法,像你的Application_Start:

ServicePointManager.DefaultConnectionLimit = 200; 

注:此代碼爲基礎的方法也同樣適用於non-ASP.NET的應用程序,所以你可以使用它作爲一個「通用」解決方案,如果你想避免.config。