2009-04-30 97 views
0

我目前正在編寫一個站點地圖生成器,它爲站點刮取網址並生成一個XML站點地圖。由於大部分等待都花在了對uri的請求上,所以我正在使用線程,特別是ThreadPool對象中的構建。在C#中線程的未知數量的線程#

爲了讓主線程等待未知數量的線程完成我已經實現了以下設置。我不覺得這是一個很好的解決方案,任何線程專家都可以告訴我這個解決方案有什麼問題,或者提出一個更好的實現方法?

的的EventWaitHandle設置爲EventResetMode.ManualReset

這裏是線程方法

protected void CrawlUri(object o) 
    { 

     try 
     { 
      Interlocked.Increment(ref _threadCount); 
      Uri uri = (Uri)o; 

      foreach (Match match in _regex.Matches(GetWebResponse(uri))) 
      { 
       Uri newUri = new Uri(uri, match.Value); 

       if (!_uriCollection.Contains(newUri)) 
       { 
        _uriCollection.Add(newUri); 
        ThreadPool.QueueUserWorkItem(_waitCallback, newUri); 
       } 
      } 
     } 
     catch 
     { 
      // Handle exceptions 
     } 
     finally 
     { 
      Interlocked.Decrement(ref _threadCount); 
     } 

     // If there are no more threads running then signal the waithandle 
     if (_threadCount == 0) 
      _eventWaitHandle.Set(); 
    } 

這裏是主線程方法

// Request first page (based on host) 
Uri root = new Uri(context.Request.Url.GetLeftPart(UriPartial.Authority)); 

// Begin threaded crawling of the Uri 
ThreadPool.QueueUserWorkItem(_waitCallback, root); 
Thread.Sleep(5000); // TEMP SOLUTION: Sleep for 5 seconds 
_eventWaitHandle.WaitOne(); 

// Server the Xml Sitemap 
context.Response.ContentType = "text/xml"; 
context.Response.Write(GetXml().OuterXml); 

任何想法非常讚賞:)

回答

1

那麼,首先您可以創建一個未設置的ManualResetEvent,因此您在等待之前不必休眠。其次,你需要把你的Uri系列的線程同步。您可能會遇到兩個線程通過「此Uri尚不存在」檢查並添加重複項的競爭條件。另一種競爭條件是兩個線程可以通過if (_threadCount == 0)檢查,他們都可以設置事件。

最後,通過使用異步BeginGetRequest,可以使整個事情變得更高效。現在你的解決方案可以保持一個線程來等待每個請求。如果你使用異步方法和回調,你的程序將使用更少的內存(每個線程1MB),並且不需要執行線程的上下文切換。

下面是一個應該說明我在說什麼的例子。出於好奇,我測試了它(具有深度限制),它確實有效。

public class CrawlUriTool 
{ 
    private Regex regex; 
    private int pendingRequests; 
    private List<Uri> uriCollection; 
    private object uriCollectionSync = new object(); 
    private ManualResetEvent crawlCompletedEvent; 

    public List<Uri> CrawlUri(Uri uri) 
    { 
     this.pendingRequests = 0; 
     this.uriCollection = new List<Uri>(); 
     this.crawlCompletedEvent = new ManualResetEvent(false); 
     this.StartUriCrawl(uri); 
     this.crawlCompletedEvent.WaitOne(); 

     return this.uriCollection; 
    } 

    private void StartUriCrawl(Uri uri) 
    { 
     Interlocked.Increment(ref this.pendingRequests); 

     HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); 

     request.BeginGetResponse(this.UriCrawlCallback, request); 
    } 

    private void UriCrawlCallback(IAsyncResult asyncResult) 
    { 
     HttpWebRequest request = asyncResult.AsyncState as HttpWebRequest; 

     try 
     { 
      HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult); 

      string responseText = this.GetTextFromResponse(response); // not included 

      foreach (Match match in this.regex.Matches(responseText)) 
      { 
       Uri newUri = new Uri(response.ResponseUri, match.Value); 

       lock (this.uriCollectionSync) 
       { 
        if (!this.uriCollection.Contains(newUri)) 
        { 
         this.uriCollection.Add(newUri); 
         this.StartUriCrawl(newUri); 
        } 
       } 
      } 
     } 
     catch (WebException exception) 
     { 
      // handle exception 
     } 
     finally 
     { 
      if (Interlocked.Decrement(ref this.pendingRequests) == 0) 
      { 
       this.crawlCompletedEvent.Set(); 
      } 
     } 
    } 
} 
+0

這看起來很有趣,謝謝。我希望有機會在週末嘗試一下。我不能相信我忘記了異步請求:o – WDuffy 2009-05-01 07:03:28

0

當我做這種邏輯時,我一般會嘗試使一個對象代表每個異步任務以及它需要運行的數據。我通常會將此對象添加到要完成的任務集合中。線程池獲取這些任務的時間安排,並且當任務完成時我會讓對象自己從「待完成」集合中移除,可能會在集合本身發出信號。

因此,當「待完成」集合爲空時,您就完成了;主線程可能每完成一項任務就會喚醒一次。

0

你可以看看Task Parallel Library的CTP,這應該使你更簡單。你在做什麼可以分爲「任務」,大塊或工作單元,如果你提供任務,TPL可以爲你並行。它也在內部使用線程池,但它更易於使用並帶有許多選項,如等待所有任務完成。請查看this Channel9視頻,其中介紹了各種可能性,並演示了以並行遞歸方式遍歷樹的演示,這似乎非常適用於您的問題。

但是,它仍然是一個預覽版,並且不會在.NET 4.0之前發佈,因此它不附帶任何保證,您必須手動將提供的System.Threading.dll(在安裝文件夾中找到)包含到你的項目,我不知道這是否是你的選擇。

+0

Julian真的很有趣。我不能在這個實現中使用它,但我會defo正在研究它。謝謝 :) – WDuffy 2009-05-01 06:57:06