(0) exportieren Drucken
Alle erweitern
Erweitern Minimieren

Überlegungen zur Leistung beim Erstellen von Webdienstaufrufen von ASPX-Seiten

Veröffentlicht: 09. Okt 2003 | Aktualisiert: 27. Jun 2004
Von Matt Powell

Matt Powell zeigt, wie Sie Leistungsprobleme vermeiden, und erläutert den Verbrauch von Threadpool-Ressourcen durch einen asynchronem Ansatz für Webdienstaufrufe, die Microsoft ASP.NET verwenden.

Dieser Artikel enthält auch Links zu englischsprachigen Seiten.

Download
Auf dieser Seite

 Szenario: Leistungsverbrauch beim Aufruf eines Webdienstes von einer ASP.NET-Seite
 Optimierung rund um das Problem
 Die Lösung des Problems: Asynchrone Verarbeitung der Anforderungen
 Schlussfolgerung

Webdienstaufrufen_ASPX_02.gif

Rund um die Erstellung von Webinhalten entwickelt sich hier bei Microsoft eine spannende Dynamik. Sie können sich vorstellen, dass viele Personen aus unterschiedlichen Gruppen bei Microsoft nötig sind, um die Mengen technischer Informationen auf Microsoft.com zu erstellen. Die Mitarbeiter, die den Inhalt erstellen, setzen seit kurzem neue Themenschwerpunkte. Zu diesen Mitarbeitern gehören der Microsoft-Produktsupport (PSS), der den größten Teil der Knowledge Base verfasst, MSDN und verschiedene andere. Die verbreitete Frage von Leistungsproblemen beim Ausführen von Webdienstaufrufen von ASP.NET-Webseiten wurde ursprünglich vom PSS erkannt. Wir fanden, dass das Problem ausführlicher als in einem Knowledge Base-Artikel behandelt werden sollte, aber trotzdem nicht in den Rahmen einer Produktdokumentation passte.

Das Thema ist aber perfekt für eine Kolumne.

Szenario: Leistungsverbrauch beim Aufruf eines Webdienstes von einer ASP.NET-Seite

Wenn wir in dieser Kolumne über Webdienste sprechen, gehen wir von der Voraussetzung aus, dass diese in einer großen Vielfalt von Szenarios verwendet werden. Ein Hauptszenario ist der Zugriff auf Webdienste von einer Umgebung der mittleren Ebene aus, wie ASP.NET-Webseiten. Die Supportmitarbeiter für den MapPoint .NET-Webdienst hatten wiederholt ein Problem mit den Nutzern ihres Webdienstes. Ein Aufruf von MapPoint .NET kann zu einem relativ langen Webdienstaufruf führen. Das ist für sich betrachtet kein Problem. Es gibt aber einige andere Faktoren, die das scheinbar geringe Problem vielfach vergrößern.

HTTP-Beschränkung auf zwei Verbindungen
Die HTTP-Spezifikation gibt vor, dass ein HTTP-Client maximal zwei TCP-Verbindungen gleichzeitig zu einem einzelnen Server herstellt. So wird verhindert, dass ein einzelner Browser einen Server mit Verbindungsanforderungen überlastet, wenn beispielsweise eine Seite mit 120 eingebetteten Miniaturansichtsbildern geöffnet wird. Der Browser erstellt keine 120 TCP-Verbindungen und sendet für jede eine HTTP-Anforderung. Er begnügt sich stattdessen mit 2 Verbindungen und beginnt dann, die 120 HTTP-Anforderungen für die Miniaturansichten über diese zwei Leitungen zu senden. Der Haken bei diesem Ansatz auf mittlerer Ebene ist, dass die mittlere Ebene möglicherweise 50 Benutzer gleichzeitig hat. Wenn für jeden dieser Benutzer ein MapPoint .NET-Webdienst aufgerufen wird, dann stehen 48 Benutzer Schlange, während sie darauf warten, dass eine der beiden Leitungen frei wird.

Threadpool-Begrenzungen
ASP.NET bearbeitet eingehende Anforderungen mithilfe eines Pools von Threads, dem so genannten Prozess-Threadpool. Normalerweise geht eine Anforderung ein und wird von einem untätigen Thread aus dem Pool verarbeitet. Das "Problem" dabei ist nun, dass der Prozess-Threadpool keine unendliche Anzahl von Threads erstellt, um eine große Anzahl Anforderungen zu bewältigen. Eine maximale Anzahl von Threads ist sinnvoll: Bei unendlich vielen Threads würden wir die gesamten Serverressourcen ausschließlich zum Verwalten dieser Threads einsetzen. Durch die Begrenzung der Anzahl der erzeugten Threads bleibt der Aufwand für die Verwaltung der Threads auf einem akzeptablen Niveau. Trifft eine Anforderung ein und alle Threads im Pool werden bereits verwendet, dann wird diese Anforderung einfach in die Warteschlange gestellt, bis einer der verwendeten Threads die Verarbeitung beendet und frei wird, um die neue Anforderung zu bearbeiten. Diese Methode ist tatsächlich effizienter als das Wechseln zu einem neuen Thread, da zwischen den Anforderungen kein Austausch von Threads erforderlich ist. Es kann aber ein Problem entstehen, wenn Threads nicht effizient verwendet werden und immer mehr Anforderungen in die Warteschlange eingereiht werden, besonders auf einem viel verwendeten Webserver.

Betrachten Sie folgendes Szenario: Sie führen einen Webdienstaufruf von Ihrer ASP.NET-Seite aus. Bei einem synchronen Aufruf wird der Thread, den Sie beanspruchen, blockiert, bis dieser Webdienstaufruf beendet ist. Der Thread kann während des Aufrufs nichts anderes verarbeiten. Obwohl er nur wartet, kann der Thread keine andere Anforderung bearbeiten. Geht man von der Standardanzahl von 20 Arbeitsthreads auf einem Computer mit Einzelprozessor aus, dann sind nur 20 gleichzeitige Anforderungen erforderlich, bis alle Ihre Threads verwendet werden und weitere Anforderungen warten müssen.

Das Problem ist nicht auf Webdienste beschränkt
Überlange, blockierende Aufrufe von einer Webseite sind kein Problem, das sich auf den Aufruf von Webdiensten beschränkt. Bei einer ganzen Reihe von überlangen Aufrufen stoßen Sie auf das gleiche Problem: SQL ServerT-Anforderungen, überlanges Lesen oder Schreiben einer Datei, Webanforderungen jeder Art oder Zugriff auf eine parallele Ressource, wobei Sperren zu erheblichen Verzögerungen führen können. Es gibt auch viele unproblematische Webdienstszenarios, in denen der Aufruf des Dienstes schnell genug ist. Wenn Sie aber den MapPoint .NET-Webdienst möglicherweise über Proxys aufrufen, über Verbindungen mit einer gewissen Latenz, und der Service ein wenig Zeit zur Bearbeitung der Anforderung benötigt, dann wird sichtbar, wo die Verzögerungen und Probleme entstehen können, wenn die Sites vielfach verwendet werden.

Optimierung rund um das Problem

Einige Aspekte des Problems können einfach durch bestimmte Konfigurationseinstellungen der Umgebung verbessert werden. Werfen wir einen Blick auf einige dieser Konfigurationseinstellungen.

maxconnections
Die Standardbegrenzung auf zwei Verbindungen für die Verbindung mit einer Webressource kann über das Konfigurationselement connectionManagement gesteuert werden. Sie können in der connectionManagement-Einstellung Namen von Sites hinzufügen, für die Sie eine vom Standard abweichende Einstellung benötigen. Die folgende Einstellung kann einer typischen Web.config-Datei hinzugefügt werden, um den Standardwert für alle Server, mit denen Sie eine Verbindung herstellen, auf eine Verbindungsbegrenzung von 40 hochzusetzen.

<configuration> 
  <system.net> 
 <connectionManagement> 
   <add address="*" maxconnection="40" /> 
 </connectionManagement> 
  </system.net> 
  <system.web> 
  ...

Wenn Sie mit Ihrem lokalen Host verbinden, hat diese Einstellung keine Auswirkungen, da die Anzahl der Verbindungen zu Ihrem lokalen Rechner nie begrenzt ist.

"maxWorkerThreads" und "minFreeThreads"
Wenn Sie HTTP-503-Fehler erhalten ("Der Dienst ist zurzeit überlastet."), stehen keine Threads mehr in Ihrem Pool zur Verfügung und die Anforderungswarteschlange ist überlastet (die Standardeinstellung für appRequestQueueLimit ist 100). Bei IIS 5.0-Installationen kann die Threadpoolgröße einfach erhöht werden. Bei IIS 6.0-Installationen (die nicht im IIS 5.0-Kompatibilitätsmodus ausgeführt werden) haben diese Einstellungen keine Wirkung.

maxWorkerThreads und maxIoThreads steuern die Anzahl der Arbeitsthreads und der Threads, die neue Anforderungen für ASP.NET übergeben. Diese Einstellungen müssen in Machine.config vorgenommen werden und wirken sich auf alle Webanwendungen aus, die auf Ihrem Computer ausgeführt werden. maxWorkerThreads ist Teil des processModel-Elements in Machine.config. Bei einer Überprüfung sehen Sie, dass der Standardwert für diese Einstellung 20 Threads pro Prozessor beträgt.

Sie können die minFreeThreads-Einstellung in Machine.config vornehmen oder innerhalb Ihrer Anwendung in der Datei Web.config unter dem httpRuntime-Element. Diese Einstellung sorgt dafür, dass kein Thread aus dem Threadpool zur Bearbeitung einer eingehenden HTTP-Anforderung verwendet wird, wenn die Anzahl der freien Threads unterhalb der festgelegten Einstellung liegt. Eine nützliche Einstellung, wenn Sie einen Prozessthread aus dem Threadpool benötigen, um eine ausstehende Anforderung zu beenden. Wenn alle Threads verwendet werden, um eingehende HTTP-Anforderungen zu bearbeiten, und die Anforderungen auf einen weiteren Thread warten, um die Verarbeitung abzuschließen, entsteht eine Deadlocksituation. Ein mögliches Beispiel für eine solche Situation: Von Ihrer ASP.NET-Anwendung führen Sie asynchrone Webdienstaufrufe aus und warten auf die Rückruffunktion, um die Anforderung abzuschließen. Für den Rückruf muss einer der freien Threads im Prozess-Threadpool verwendet werden. In Machine.config erkennen Sie, dass die minFreeThreads-Einstellung standardmäßig mit 8 angegeben wurde. Bei einem Arbeits-Threadpool mit einer Begrenzung von 20 reicht das vermutlich aus, wenn Sie die Poolgröße aber auf 100 erhöhen, kann dieser Wert zu niedrig sein.

Threadpool-Begrenzungen verschlechtern sich, wenn Ihre ASP.NET-Anwendung Webdienste auf dem lokalen Computer aufruft. Die Testanwendung, die ich für diese Kolumne erstellt habe, ruft zum Beispiel einen Webdienst auf dem gleichen Computer wie auch die ASPX-Seiten auf. Um Aufrufe zu blockieren, wird für die ASPX-Seite ebenso wie für die ASMX-Webdienstanforderung ein Thread verwendet. Dadurch wird die Anzahl der gleichzeitigen Anforderungen, die unser Webserver verarbeitet, effektiv verdoppelt. In dem Szenario, in dem wir zwei Webdienstanforderungen gleichzeitig ausführen (indem wir asynchrone Webdienstaufrufe verwenden), verdreifachen wir letztendlich die Anzahl der gleichzeitigen Anforderungen. Um diese Art von Problemen beim Rückruf an den lokalen Computer zu vermeiden, können Sie überlegen, Ihre Anwendung so zu entwickeln, dass der Code in der Webmethode einfach direkt von Ihrem ASPX-Code ausgeführt wird.

Windows XP-Einschränkungen
Wenn Sie Tests auf einem Computer mit Windows® XP ausführen, gibt es eine weitere Einschränkung: Die Anzahl gleichzeitiger Verbindungen ist für den XP-Webserver künstlich begrenzt. Windows XP ist keine Serverplattform, und die Anzahl gleichzeitiger Verbindungen ist daher auf 10 begrenzt. Normalerweise reicht das zum Testen in einer Entwicklungsumgebung aus, aber es kann zu großen Einschränkungen führen, wenn Sie Belastungstests ausführen. Verbindungen von einem lokalen Computer zählen für diese Begrenzung nicht.

Die Lösung des Problems: Asynchrone Verarbeitung der Anforderungen

Man kann natürlich die Konfigurationseinstellungen rund um das Problem optimieren. Sie können aber auch Ihre Webanwendung so entwickeln, dass das Problem nicht mehr in Erscheinung tritt. Eine optimale Skalierung für Threads, die darauf warten, dass blockierende Aufrufe beendet werden, gibt es nicht. Die Lösung kann also nur darin bestehen, das Blockierungsproblem vollständig zu vermeiden. Der richtige Weg ist die asynchrone Bearbeitung der Anforderungen. Dies zeigt sich in zwei Bereichen: die Ausführung von asynchronen Webdienstaufrufen und die asynchrone Verarbeitung von Anforderungen in Ihrer ASP.NET-Webanwendung.

Der erste Teil der asynchronen Lösung: Asynchrone Aufrufe von Webdiensten
In einer früheren Kolumne habe ich bereits über den asynchronen Aufruf von Webdiensten geschrieben. Dass Threads nicht mehr auf die Beendigung von Webdienstaufrufen warten, ist ein wichtiger Bestandteil der Erstellung eines asynchronen Seitenverarbeitungsmodells. Es stehen mehr Threads zur Verfügung und so können mehr Anforderungen verarbeitet werden. Es ist auch relativ einfach, Webdienste asynchron aufzurufen.

Betrachten Sie den folgenden Visual Basic®.NET-Code für eine ASPX-Seite:

' Excruciatingly Bad Performance Page based off of Synchronous 
' Web service calls! 
Public Class SyncPage 
 Inherits System.Web.UI.Page 
 Protected WithEvents Label1 As System.Web.UI.WebControls.Label 
 Protected WithEvents Label2 As System.Web.UI.WebControls.Label 
 Private Sub Page_Load(ByVal sender As System.Object, _ 
   ByVal e As System.EventArgs) Handles MyBase.Load 
  'Call the web service 
  Dim proxy As New localhost.Service1 
  Label1.Text = proxy.Method1(500) 
  Label2.Text = proxy.Method1(200) 
 End Sub 
End Class

Dieser Code ist ziemlich einfach. Wenn die Seite geladen wird, wird eine Instanz eines Webdienstproxys erstellt. Diese Instanz führt zwei Aufrufe einer Webmethode namens Method1 aus. Method1 gibt einfach eine Zeichenfolge mit dem Eingabeparameter, der an die Methode übergeben wird, zurück. Um diesem System eine Latenz hinzuzufügen, ist Method1 für 3 Sekunden im Ruhezustand, bevor die Methode die Zeichenfolge zurückgibt. Die zurückgegebenen Zeichenfolgen aus den Aufrufen von Method1 werden in den Text für zwei Bezeichnungen auf unserer ASPX-Seite platziert. Diese Seite hat eine sehr schlechte Leistung; sie saugt Threads aus dem Threadpool auf wie ein Schwamm. Aufgrund der Verzögerung von 3 Sekunden in der Webmethode Method1 dauert ein einziger Aufruf mindestens 6 Sekunden, bevor er abgeschlossen ist.

Der folgende Auszug zeigt den Code für eine ähnliche Webseite, bei der die Webdienstaufrufe aber asynchron ausgeführt werden.

Public Class AsyncPage 
 Inherits System.Web.UI.Page 
 Protected WithEvents Label1 As System.Web.UI.WebControls.Label 
 Protected WithEvents Label2 As System.Web.UI.WebControls.Label 
 Private Sub Page_Load(ByVal sender As System.Object, _ 
   ByVal e As System.EventArgs) Handles MyBase.Load 
  'Call the web service 
  Dim proxy As New localhost.Service1 
  Dim res As IAsyncResult  
   = proxy.BeginMethod1(500, Nothing, Nothing) 
  Dim res2 As IAsyncResult  
   = proxy.BeginMethod1(200, Nothing, Nothing) 
  Label1.Text = proxy.EndMethod1(res) 
  Label2.Text = proxy.EndMethod1(res2) 
 End Sub 
End Class

Die Seite erstellt wieder einen Webdienstproxy und führt anschließend zwei Aufrufe der Webmethode Method1 aus. Der Unterschied besteht darin, dass wir nicht direkt Method1 aufrufen, sondern BeginMethod1. BeginMethod1 wird sofort zurückgegeben, sodass wir den zweiten Aufruf an die Methode starten können. Wir starten jetzt beide Aufrufe gleichzeitig, anstatt wie im ersten Beispiel auf den Abschluss des ersten Webdienstaufrufs zu warten. Bis der konkrete Aufruf abgeschlossen ist, werden Aufrufe von EndMethod1 einfach blockiert.

Eine wichtige Feststellung ist, dass die Antwort an den Client gesendet wird, sobald wir von unserer ASPX-Seite zurückgeben. Wir können daher nicht von unserer Page_Load-Methode zurückgeben, solange wir nicht die erforderlichen Daten haben. Wir müssen aus diesem Grund blockieren, bis die Webdienstaufrufe abgeschlossen sind. Die gute Nachricht dabei ist, dass jetzt beide Aufrufe gleichzeitig ausgeführt werden. Die Verzögerung reduziert sich damit von vorher 6 auf ungefähr 3 Sekunden. Ein Fortschritt, aber immer noch werden blockierende Threads erzeugt. Der Thread, der die HTTP-Anforderung verarbeitet, muss unbedingt frei werden, während die Webdienstaufrufe abgeschlossen werden. Das Verarbeitungsmodell für ASPX-Seiten enthält aber kein asynchrones Ausführungsparadigma. ASP.NET bietet allerdings eine Lösung für dieses Problem.

Der zweite Teil der asynchronen Lösung: Asynchrone Ausführung von "PreRequestHandler"
ASP.NET unterstützt HttpHandlers. HttpHandlers sind Klassen, die die IHttpHandler-Schnittstelle implementieren. Sie werden für die Bearbeitung von HTTP-Anforderungen von Dateien mit bestimmten Erweiterungen verwendet. Wenn Sie beispielsweise Machine.config betrachten, finden Sie dort eine Anzahl von HttpHandlers, die Anforderungen von Dateien mit Erweiterungen wie .asmx, .aspx, .ashx und sogar .config bearbeiten. ASP.NET prüft bei einer Anforderung einer Datei mit einer bestimmten Endung die Konfigurationsinformationen und ruft dann den dazugehörigen HttpHandler auf, um die Anforderung zu bearbeiten.

ASP.NET bietet auch Unterstützung für das Schreiben von Ereignishandlern, die zu verschiedenen Zeiten während der Verarbeitung einer HTTP-Anforderung benachrichtigt werden können. Ein solches Ereignis ist das PreRequestHandlerExecute-Ereignis, das auftritt, bevor der HttpHandler für eine bestimmte Anforderung aufgerufen wird. Asynchrone Unterstützung wird auch für PreRequestHandlerExecute-Benachrichtigungen geboten, die zur Verwendung der AddOnPreRequestHandlerExecuteAsync-Methode der HttpApplication-Klasse registriert werden können. Die Klasse HttpApplication ist die Klasse, von der Sie ableiten, wenn Sie über Ereignishandler verfügen, die Sie nach der Global.asax-Datei erstellt haben. Wir verwenden die asynchrone PreRequestHandler-Option als asynchronen Ausführungsmodus für unsere Webdienstaufrufe.

Bevor Sie AddOnPreRequestHandlerExecuteAsync aufrufen, müssen Sie als Erstes eine BeginEventHandler- und eine EndEventHandler-Funktion erstellen. Wenn eine Anforderung eingeht, wird die BeginEventHandler-Funktion aufgerufen. An dieser Stelle starten wir unsere asynchronen Webdienstaufrufe. Der BeginEventHandler muss eine IAsyncResult-Schnittstelle zurückgeben. Wenn Sie einen einzelnen Webdienstaufruf ausführen, können Sie einfach die IAsyncResult-Schnittstelle zurückgeben, die von Ihrer Webdienstfunktion begin zurückgegeben wird (in unserem Beispiel gibt die BeginMethod1-Methode eine IAsyncResult-Schnittstelle zurück). In meinem Beispiel wollte ich die gleichen Operationen ausführen wie in unseren Beispielen für Webseiten vorher, in denen ich synchrone und asynchrone Webdienstaufrufe dargestellt habe. Daher musste ich eine eigene IAsyncResult-Schnittstelle erstellen. Mein Code für den BeginEventHandler lautet wie folgt:

Public Function BeginPreRequestHandlerExecute( 
  ByVal sender As Object, _ 
  ByVal e As EventArgs, _ 
  ByVal cb As AsyncCallback, _ 
  ByVal extraData As Object) As IAsyncResult 
 If Request.Url.AbsolutePath _ 
  = "/WebApp/PreRequestHandlerPage.aspx" Then 
  Dim proxy As MyProxy = New MyProxy 
  proxy.Res = New MyAsyncResult 
  proxy.Res.result1  
   = proxy.BeginMethod1( _ 
 500, _ 
 New AsyncCallback(AddressOf MyCallback), _ 
 proxy) 
  proxy.Res.result2  
   = proxy.BeginMethod1( _ 
 300, _ 
 New AsyncCallback(AddressOf MyCallback), _ 
 proxy) 
  proxy.Res.Callback = cb 
  proxy.Res.State = extraData 
  proxy.Res.Proxy = proxy 
  Return proxy.Res 
 End If 
 Return New MyAsyncResult 
End Function

Ich möchte Sie auf einige weitere Besonderheiten in diesem Code aufmerksam machen. Zunächst einmal wird er für jede HTTP-Anforderung, die von diesem virtuellen Verzeichnis bearbeitet wird, aufgerufen. Als Erstes überprüfe ich darum den tatsächlichen Pfad der Anforderung, um zu prüfen, ob sie sich an die Seite richtet, die ich bearbeite, oder nicht.

Meine Funktion wird mit ein paar interessanten Eingabeparametern aufgerufen. Der cb-Parameter ist die von ASP.NET übergebene Rückruffunktion. ASP.NET erwartet, dass diese bereitgestellte Rückruffunktion aufgerufen wird, wenn mein asynchroner Vorgang abgeschlossen ist. Auf diese Weise wird letztendlich mitgeteilt, wann mein EndEventHandler aufzurufen ist. Wenn ich nur einen Webdienst aufrufen würde, könnte ich den Rückruf einfach an den BeginMethod1-Aufruf übergeben und der Webdienstaufruf würde sich um den Aufruf der Funktion kümmern. Aber in diesem Fall führe ich zwei separate Aufrufe aus. Aus diesem Grund erstelle ich eine Rückruffunktion dazwischen. Ich übergebe diese Funktion den zwei BeginMethod1-Aufrufen und prüfe innerhalb meines Rückrufcodes, ob beide Aufrufe abgeschlossen sind. Wenn dies nicht der Fall ist, kehre ich zurück, wenn dies aber zutrifft, rufe ich den ursprünglichen Rückruf auf. Ebenfalls interessant ist der extraData-Parameter, der beim Aufruf den Status für ASP.NET enthält. Da ich die Statusinformation zurückgeben muss, wenn ich die Rückruffunktion aufrufe, die der cb-Parameter angibt, speichere ich sie in der IAsyncResult-Klasse, die ich erstellt habe. Der Code für meine Rückruffunktion lautet wie folgt:

Public Sub MyCallback(ByVal ar As IAsyncResult) 
 Dim proxy As MyProxy = ar.AsyncState 
 If proxy.Res.IsCompleted Then 
  proxy.Res.Callback.Invoke(proxy.Res) 
 End If 
End Sub

Die Klasse, die IAsyncResult implementiert (MyAsyncResult genannt), habe ich so erstellt, dass sie den Abschluss beider Webdienstaufrufe prüft, wenn die IsCompleted-Eigenschaft abgefragt wird.

In meinem EndEventHandler erhalte ich einfach die Ergebnisse meiner Webdienstaufrufe und speichere diese Ergebnisse im aktuellen Anforderungskontext. Der Kontext ist identisch mit dem, der dem HttpHandler übergeben wird. In diesem Fall ist dies der Handler für .aspx-Anforderungen, sodass die Informationen in meinem normalen Code verfügbar sind. Mein Code für den EndEventHandler lautet wie folgt:

Public Sub EndPreRequestHandlerExecute(ByVal ar As IAsyncResult) 
 If Request.Url.AbsolutePath _ 
  = "/WebApp/PreRequestHandlerPage.aspx" Then 
  Dim res As MyAsyncResult = ar 
  Dim proxy As MyProxy = res.Proxy 
  Dim retString As String 
  retString = proxy.EndMethod1(proxy.Res.result1) 
  Context.Items.Add("WebServiceResult1", retString) 
  retString = proxy.EndMethod1(proxy.Res.result2) 
  Context.Items.Add("WebServiceResult2", retString) 
 End If 
End Sub

Da die Daten für meine .aspx-Seite bereits empfangen wurden, ist die Verarbeitung für die eigentliche Seite ziemlich einfach.

Public Class PreRequestHandlerPage 
 Inherits System.Web.UI.Page 
 Protected WithEvents Label1 As System.Web.UI.WebControls.Label 
 Protected WithEvents Label2 As System.Web.UI.WebControls.Label 
 Private Sub Page_Load(ByVal sender As System.Object, _ 
  ByVal e As System.EventArgs) Handles MyBase.Load 
  Label1.Text = Context.Items("WebServiceResult1") 
  Label2.Text = Context.Items("WebServiceResult2") 
 End Sub 
End Class

Nicht bloß Theorie - es funktioniert!
Es leuchtet ein, dass ich weniger Ressourcen verbrauche, wenn ich nicht alle meine Threads blockiere. Aber gibt es wirklich einen Unterschied in den Ergebnissen? Die Antwort darauf ist ein gewaltiges JA! Ich fasse die drei Textszenarios dieser Kolumne zusammen: Ausführen von 2 blockierenden Aufrufen durch Webseitencode, Ausführen von 2 asynchronen Aufrufen durch Webseitencode und Ausführen von 2 asynchronen Aufrufen durch PreRequestHandler-Code. Für die Belastungstests für die drei Szenarios habe ich den Microsoft Application Center Test verwendet, der über einen Zeitraum von 60 Sekunden fortlaufende Anforderungen von 100 virtuellen Clients sendete. Die Ergebnisse, die Sie in der Abbildung unten sehen, geben die Anzahl der abgeschlossenen Anforderungen innerhalb dieses Zeitraums an.

Webdienstaufrufen_ASPX_01.gif

Abbildung 1. In 60 Sekunden abgeschlossene Anforderungen mit 100 Clients zur gleichen Zeit

Die asynchrone PreRequestHandler-Lösung verarbeitet fast 8-mal so viele Anforderungen wie die zweitschnellste Lösung! Mithilfe dieser Lösung können Sie also mehr Anforderungen verarbeiten. Aber wie lange dauert eine einzelne Anforderung wirklich bis zu ihrem Abschluss? Sie sehen hier die durchschnittlichen Antwortzeiten für die drei Ansätze:

Webdienstaufrufen_ASPX_02.gif

Abbildung 2. Durchschnittliche, abgeschlossene Antwortzeiten mit 100 Clients zur gleichen Zeit

Die durchschnittliche Antwortzeit für Anforderungen bei Verwendung des PreRequestHandler lag bei nur 3,2 Sekunden. Bei einer eingebauten Verzögerung von 3 Sekunden für jeden Webdienstaufruf ist dies eine höchst effiziente Lösung des Problems.

Natürlich muss ich dazu sagen, dass diese unwissenschaftlichen Zahlen meinem unwissenschaftlichen Computer in meinem ebenfalls sehr unwissenschaftlichen Büro entstammen. Aber es ist klar, dass die Leistung sich verbessern muss, wenn untätige Threads frei werden und tatsächlich zur Verwendung kommen. Diese Ergebnisse zeigen hoffentlich, dass die Verbesserungen einen erheblichen Unterschied machen können.

Der PreRequestHandler-Ansatz ist notwendig: Es existiert kein Handlingmechanismus für asynchrone Anforderungen im Handler für .aspx-Anforderungen. Das gilt nicht für alle ASP.NET-HTTP-Handler. Der .asmx-Anforderungshandler für Webdienste verfügt über ein asynchrones Modell, das ich in einer früheren Kolumne beschrieben habe. Die PreRequestHandler-Lösung funktioniert für alle Arten von ASP.NET-Anforderungen. Die Programmierung ist mit der asynchronen Unterstützung des .asmx-Handlers aber etwas leichter als mit dem PreRequestHandler.

Schlussfolgerung

Wenn Sie einen überlangen Prozess gleich welcher Art ausführen, bei dem Leistung problematisch ist, können Sie immer ein asynchrones Ausführungsmodell in Betracht ziehen. Beim Aufruf eines Webdienstes von einer .aspx-Seite haben wir festgestellt, dass wir asynchrone Webdienstaufrufe mit einem asynchronen Ausführungsparadigma, das ASP.NET bereitstellt, kombinieren können. Das Problem der fehlenden asynchronen Unterstützung bei der Verarbeitung von .aspx-Anforderungen wurde durch diese Kombination gelöst. Probleme mit der Leistung und der übermäßige Verbrauch von Threadpool-Ressourcen wurden mit dieser asynchronen Vorgehensweise behoben.


Anzeigen:
© 2014 Microsoft