2011-12-07 51 views
2

我的網站列表和代理服務器的列表中的任務。並行化使用.AsParallel()或的ForAll性能Parallel.ForEach問題

我有這個動作

Action<string> action = (string url) => 
{ 
    var proxy = ProxyHandler.GetProxy(); 
    HtmlDocument html = null; 
    while (html == null) 
    { 
     try 
     { 

      html = htmlDocumentLoader.LoadDocument(url, proxy.Address); 

      // Various db manipulation code 

      ProxyHandler.ReleaseProxy(proxy); 
     } 
     catch (Exception exc) 
     { 
      Console.WriteLine("{0} proxies remain", ProxyHandler.ListSize()); 

      // Various db manipulation code 

      proxy = ProxyHandler.GetProxy(); 
     } 
    } 
}; 

我稱之爲使用

urlList.AsParallel().WithDegreeOfParallelism(12).ForAll(action); 

Parallel.ForEach(urlList, action); 

我ProxyHandler類是如下

public static class ProxyHandler 
{  
    static List<Proxy> ProxyList = new ProxyRepository().GetAliveProxies().ToList(); 

    public static Proxy GetProxy() 
    { 
     lock (ProxyList) 
     { 
      while (ProxyList.Count == 0) 
      { 
       Console.WriteLine("Sleeping"); 
       Thread.Sleep(1000); 
      } 
      var proxy = ProxyList[0]; 
      ProxyList.RemoveAt(0); 
      return proxy; 
     }   
    } 

    public static void ReleaseProxy(Proxy proxy) 
    { 
     lock (ProxyList) 
     { 
      if(!ProxyList.Contains(proxy))ProxyList.Add(proxy); 
     } 
    } 

    public static int ListSize() 
    { 
     lock (ProxyList) 
     { 
      return ProxyList.Count; 
     } 
    } 
} 

我的問題是,當它執行時,它看起來很快完成〜90%的網站,然後花費很長時間來完成剩下的工作。

我的意思是從100個URL,因爲它確實做着最後的10

我已經排除了代理是死的,因爲沒有拋出異常要花多少時間做的第一件90。看起來好像urlList上的最後一項只需要很長時間才能完成。

UPDATE:

我加入了一些運行數據,使我更清楚的問題:

Minute 1 2 3 4 5 6 7 8 9 16 18 19 
Count 23 32 32 17 6 1 1 1 1 2 1 2 

正如你在第4分鐘我做的請求一百十九分之一百零四見。然後剩下15分鐘。

這看起來像在線程的加盟問題,但我不能發現什麼,這可能是。

+2

您是否嘗試過分析該應用程序? Visual Studio有一個併發分析器,可以給你一些線索。 –

回答

3

你是在浪費線程和CPU時間。在這種情況下,你將有12個線程;每個線程一次只能處理一個url。所以,你一次只能處理12個URL。而且,大多數時候這些線程什麼也不做(他們只會等待一個免費的代理或者一個被加載的頁面),而這些線程可以用於更多有用的任務。

爲了避免這種情況,您應該使用非阻塞IO操作。因此,不要使用htmlDocumentLoader.LoadDocument,您應該考慮使用其異步接口之一(htmlDocumentLoader.BeginLoadDocument/htmlDocumentLoader.EndLoadDocumenthtmlDocumentLoader.LoadDocumentAsync/htmlDocumentLoader.LoadDocumentCompleted)。在這種情況下,如果您有100個URL,則它們都將同時加載,而不會創建額外的線程並浪費CPU時間。只有在加載頁面時,纔會創建新線程(實際上是從ThreadPool中獲取)來處理它。

您等待免費代理的方式也是浪費。如果沒有免費代理,則使用while (ProxyList.Count == 0)來凍結線程,請考慮使用每秒喚醒的計時器並檢查空閒代理是否可用。這不是最好的解決方案,但至少它不會浪費線程。更好的解決方案是向ProxyHandler添加一個事件,該事件將在代理可用時通知。

+0

'而(ProxyList.Count == 0)'被放在那裏以防萬一,並且迄今爲止從未如此。異步頁面加載是一個好主意,但恐怕它會使我的解決方案過於複雜。 –

+0

這取決於實施。 TPL提供了將APM(BeginXXX,EndXXX)和EAM(XXXAsync,XXXCompleted)模型包裝成任務的方法。然後,您可以使用TPL輕鬆操作這些任務。另外,請看看Reactive Extensions for .NET。它爲異步操作提供了很好的工具。 –

2

您的問題可能是由於PLinq正在使用分區程序。

如果正在使用的範圍Partitiner,網址的集合被分成組,每組網址的相等(ISH)數。然後爲每個組啓動一個任務,不再進行同步。

這意味着將會有一個任務需要最長,仍然有工作,當所有其他任務已經完成的事情。這實際上意味着操作的最後部分是單線程的。

解決方案是使用不同的分區器。您可以使用內置的塊分區程序,如MSDN中所述。

如果不順利的話,你將不得不寫/找到產生元素一個接一個分區實施。這是建在C#5:EnumerablePartitionerOptions

+0

你說的話很有意義。但是這並不能解釋,例如在執行的最後幾分鐘內(總運行時間爲18-25分鐘),它看起來好像什麼都沒有發生(因爲我還沒有分析,但我正在談論控制檯寫入和數據庫日誌記錄) –