2009-04-09 13 views
12

Benutzer trifft Seite spawn.aspx die dann ein halbes Dutzend Themen laicht, Rendering Seiten alle mitmithilfe eines Httpcontext über Threads

((System.Web.IHttpHandler)instance).ProcessRequest(reference to spawn's HTTPContext); 

nicht über die Tatsache Sorgen Sie sich, dass ASP.Net scheinbar den Benutzer sendet 7 Antworten für 1 Anfrage, dieser Teil wird bearbeitet und nur eine Antwort wird gesendet.

Das Problem ist, in einer stark frequentierten Umgebung (unsere Produktionsumgebung) mit vielen Threads (Quad-Quads) erhalten wir einen Fehler:

System.IndexOutOfRangeException 
at System.collections.ArrayList.Add 
at System.Web.ResponseDependencyList.AddDependencies(String[] items, String argname, Boolean cloneArray, DateTime utcDepTime) 
at System.Web.ResponseDependencyList.AddDependencies(String[] items, String argname, Boolean cloneArray, String requestVritualPath) 
at System.Web.UI.Page.AddWrappedFileDependencies(Object virtualFileDependencies) 
at ASP.spawned_page_no_1_aspx.FrameworkInitialize() 
at System.Web.UI.Page.ProcessRequest 

Wir können nicht an andere Stelle kopieren. Mein Kollege glaubt, dass dies daran liegt, dass ich den ursprünglichen HTTPContext wiederverwende und ihn an die anderen Threads weitergebe, und dass er nicht Thread-sicher ist.

Nach dieser Logik habe ich versucht, einen neuen HTTPContext zu erstellen, um in die Threads zu gelangen. Aber Teile davon scheinen sich nicht zu "vereinigen". Insbesondere muss ich das Session-Objekt in den neuen HTTPContext holen. Ich kann mir vorstellen, dass ich auch andere Teile einbauen möchte, wie Cache. Für den Datensatz HTTPContext.Current.Session.IsSynchronized ist false.

Meine Fragen sind:

  1. Glauben Sie, der Fehler von der Verwendung Httpcontext über Threads ist?
  2. Wie kann ich es beheben?
  3. Wenn der Fix den HTTPContext für jeden Thread dupliziert, wie kann ich die Session (und Cache) in den neuen bekommen? Request und Response kommen im ctor, aber Session ist nicht einstellbar.

Edit: Details

Also auf diese Aussage zurück: „Seien Sie nicht über die Tatsache Sorgen, dass ASP.Net scheinbar wird der Benutzer 7 Antworten für 1-Anforderung sendet, wird dieser Teil behandelt und nur eine Antwort wird gesendet. " Riesiger Fan von Raymond Chen, ich stimme Ihnen zu: "Jetzt haben Sie zwei Probleme" ist eine sinnvolle Aussage in Ermangelung weiterer Informationen.

Was tatsächlich passiert ist, dass ich ein Excel-Dokument erstellen, um zurück zu senden. Auf der Seite spawn.aspx werden einige Statusinformationen erstellt, einschließlich der Tatsache, dass das Rendering in Excel ausgeführt wird, und des Objekts, für das das Rendering ausgeführt werden soll. Jede erstellte Seite erhält diese Informationen und blockiert so lange, bis sie an das Objekt gerendert werden. Wenn wahrsten Sinne des Wortes wie folgt aussieht:

protected override void Render(System.Web.UI.HtmlTextWriter writer) 
{ 
    if (this.RenderToExcel) 
    { 
     Deadlocker.SpinUntilCurrent(DeadLockToken); 
     RenderReport(this, this.XLSWriter); 
     Deadlocker.Remove(DeadLockToken); 
    } 
    else 
     base.Render(writer); 
} 

Aber alle die Verarbeitung bis zu diesem Punkt - Datenbankzugriff, Kontrolle Hierarchie, alles, was parallel durchgeführt. Und es gibt eine Menge davon - genug, um es zu parrallisieren, während es Render noch blockieren lässt, wird die Gesamtzeit in mehr als der Hälfte reduzieren.

Und der beste Teil davon ist - nichts musste für den Excel-Render neu geschrieben werden. Alle Steuerelemente wissen, wie man sich selbst übertrifft, und Sie können jede erzeugte Seite unabhängig besuchen (das ist der "normale Fall" tatsächlich - der Excel-Bericht ist nur eine Ansammlung aller erzeugten Seiten.)

Also dachte ich mir das Das Endergebnis würde lauten: "Du kannst das nicht, du musst den Ansatz überdenken" - aber ich musste es zumindest versuchen, weil alles so gut funktioniert, ohne Logik oder Code zu kopieren oder etwas zu abstrahieren ist einfach so perfekt. Und es ist nur Multi-Threading, das ist das Problem, wenn ich die Seiten seriell rendern alles ist in Ordnung, nur langsam.

Antwort

2

Ihre Kollegen haben Recht, wenn ein Thread eine Ressource sperrt und ein anderer Thread versucht, sie zu verwenden, dann wird Ihr Thread Pool boom! Nicht sehr gutes Ergebnis. Die meisten Menschen lösen dies, indem sie neue Objekte erstellen und sie in parameterisierte Threads übergeben. Wenn Sie unbedingt dasselbe Objekt verwenden müssen, müssen Sie Code implementieren, der zuerst prüft, ob die Ressource von einem anderen Thread verwendet wird, und dann kurz warten, bevor Sie die Überprüfung erneut durchführen. Ein Beispiel wäre das Erstellen eines IsInUse-Bool, das Sie immer zuerst überprüfen, dann setzt Ihr Thread es auf True, wenn es diese Ressource verwendet, und dann auf False, wenn andere Threads versuchen, die zugrunde liegende Ressource zu verwenden (Ihr httpContext) . Ich hoffe das hilft.

+0

Al diese Sperre wird wahrscheinlich schwer zu arrangieren, die Threading-Ausnahme, die er bekommt, ist von der Klasse Page, die den HTTP-Kontext mutiert, es sei denn, er kann die Aktion in der Seite, die dies tut, überschreiben und eine Sperre dann die Verriegelungslösung wird nicht funktionieren. – meandmycode

+1

Großartiger Kommentar, ich stimme dir zu. Meine Präferenz wäre, ein völlig neues Objekt zu übergeben, das einige seiner Informationen aus dem HTTP-Kontext zum Zeitpunkt der Profilerstellung ableitet. Das wäre kugelsicher. –

4

Während der HttpContext entworfen wurde, um einen Kontext zu behandeln, der nicht threadspezifisch ist (da der http-Kontext in einem Thread beginnen und auf einem anderen enden kann), ist er nicht implizit Thread-sicher.

Im Wesentlichen ist das Problem, dass Sie etwas tun, was nicht beabsichtigt ist, diese Anfragen würden im Allgemeinen mehrere sein und jeder hat seine eigene zugewiesene HttpApplication, um die Anfrage zu erfüllen, und jeder hat seinen eigenen HttpContext.

Ich würde wirklich versuchen und lassen Sie die ASP.net-Infrastruktur die Anforderungen selbst delegieren.

1

Da HttpContext Teil der .Net-Bibliothek ist, würde ich erwarten, dass alle statischen Funktionen threadsafe sind, aber alle nicht statischen Member nicht threadsicher sind, wenn sie für dieselbe Instanz des Objekts aufrufen. Hoffentlich wird das nächste Mal erwartet, dass die gemeinsame Nutzung der HttpContext-Instanz über Threads Probleme haben wird.

Gibt es eine Möglichkeit, die Operationen, die parallel vom HttpContext ausgeführt werden müssen, zu entkoppeln? Wenn sie nur Daten laden und in das CSV-Format schreiben, hat dieser Code keine Abhängigkeit von der ASP.NET-Benutzersteuerung oder dem Seitenlebenszyklus. Sobald diese Abhängigkeit entfernt wurde, können Sie die Seite mit einem asynchronen HttpHandler implementieren, wobei die parallelen Operationen zwischen IHttpHandler.BeginProcessingRequest() und IHttpHandler.EndProcessingRequest() ausgeführt werden.

1

Ich würde sicherstellen, dass jedes Mal, wenn Sie auf die HttpContext.Current.Items-Auflistung zugreifen, dass Sie einen Block (C#)/SyncLock (VB) Block auf dem HttpContext.Current.Items.SyncRoot-Objekt verwenden, um Ihre Aufrufe zu umbrechen.