2009-04-30 4 views
0

Ich schreibe gerade einen Sitemap-Generator, der eine Website nach URLs scrappt und eine XML-Sitemap erstellt. Da der größte Teil des Wartens auf Anfragen an Uri's liegt, verwende ich Threading, insbesondere das Build in ThreadPool-Objekt.Threading einer unbekannten Menge von Threads in C#

Damit der Hauptthread auf die unbekannte Anzahl von Threads warten kann, habe ich die folgende Konfiguration implementiert. Ich denke jedoch nicht, dass dies eine gute Lösung ist. Können mir Threading-Gurus irgendwelche Probleme mit dieser Lösung raten oder einen besseren Weg vorschlagen, sie zu implementieren?

Die Eventwaithandle wird auf EventResetMode.ManualReset

Hier ist der Thread-Methode

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

Hier ist der Haupt-Thread-Methode

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

Irgendwelche Ideen sehr geschätzt werden :)

Antwort

1

Nun, zuerst können Sie ein ManualResetEvent erstellen, das nicht gesetzt wird, so dass Sie nicht schlafen müssen, bevor Sie darauf warten. Zweitens müssen Sie die Thread-Synchronisation um Ihre Uri-Sammlung legen. Sie könnten eine Race-Bedingung erhalten, bei der zwei Threads den Check "Diese Uri existiert noch nicht" passieren und sie Duplikate hinzufügen. Eine weitere Race-Bedingung ist, dass zwei Threads die if (_threadCount == 0) Prüfung bestehen und beide das Event setzen könnten.

Zuletzt können Sie das Ganze viel effizienter machen, indem Sie das asynchrone BeginGetRequest verwenden. Ihre Lösung hält jetzt einen Thread bereit, um auf jede Anfrage zu warten. Wenn Sie asynchrone Methoden und Callbacks verwenden, benötigt Ihr Programm weniger Arbeitsspeicher (1MB pro Thread) und muss die Kontextwechsel von Threads nicht mehr so ​​oft ausführen.

Hier ist ein Beispiel, das veranschaulichen sollte, worüber ich spreche. Aus Neugierde habe ich es getestet (mit einer Tiefenbegrenzung) und es funktioniert.

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

Das sieht sehr interessant aus, danke. Ich werde hoffentlich die Chance haben, es am Wochenende auszuprobieren.Ich kann nicht glauben, dass ich asynchrone Anfrage vergessen habe: o – WDuffy

0

Wenn ich diese Art von Logik mache, versuche ich generell um ein Objekt zu erstellen, das jede asynchrone Aufgabe und die Daten darstellt, die es ausführen muss. Normalerweise würde ich dieses Objekt der Sammlung der auszuführenden Aufgaben hinzufügen. Der Thread-Pool erhält diese Aufgaben zeitlich verschoben, und ich würde das Objekt selbst nach Beendigung der Aufgabe aus der Sammlung "to be done" entfernen lassen, möglicherweise in der Sammlung selbst signalisierend.

Sie sind also fertig, wenn die Sammlung "to be done" leer ist; Der Haupt-Thread wird wahrscheinlich einmal durch jede abgeschlossene Aufgabe geweckt.

0

Sie in die CTP des Task Parallel Library aussehen könnte, welche diese für Sie einfacher machen sollte. Was Sie tun, kann in "Aufgaben", Chunks oder Arbeitseinheiten unterteilt werden, und die TPL kann dies für Sie parallelisieren, wenn Sie die Aufgaben bereitstellen. Es verwendet intern auch einen Thread-Pool, aber es ist einfacher zu bedienen und bietet viele Optionen wie das Warten auf den Abschluss aller Aufgaben. Schauen Sie sich this Channel9 Video an, wo die Möglichkeiten erklärt werden und wo ein Demo gezeigt wird, wie man einen Baum rekursiv parallel durchläuft, was sehr gut auf Ihr Problem zutrifft.

Es ist jedoch immer noch eine Vorschau und wird nicht veröffentlicht werden, bis .NET 4.0, so kommt es ohne Garantien und Sie müssen manuell die mitgelieferte System.Threading.dll (im Installationsordner) in enthalten Ihr Projekt und ich weiß nicht, ob das eine Option für Sie ist.

+0

Das sieht wirklich interessant aus Julian. Ich werde es bei dieser Implementierung nicht verwenden können, aber ich werde es mir nicht ansehen. Vielen Dank :) – WDuffy