Afhænger en eller flere Web Forms af længerevarende processering så kan den givne web applikation hurtigt få "åndenød" - især hvis der skulle
forvilde sig flere brugere ind på sitet samtidig :-). Som udgangspunkt er ASP.NET synkron afvikling af de enkelte Web Forms, og der er en
(forholdsvis lav) grænse for hvor mange samtidige tråde ASP.NET starter op - dette begrænser antallet af samtidige brugere.
Web Services kaldt fra en Web Form
Som eksempel så lad os antage at en Web Form har brug for at kalde en WebService. Denne WebService er krævende og tager nogle sekunder. I
disse sekunder er der på webserveren låst en tråd til Web Form'en. Ved mange samtidige brugere af websitet vil dette sløve utroligt meget
ned på performance.
I ASP.NET 1.1 kan man registrere en PreRequestHandlerExecuteAsync i Globals.asax, man kan så afvikle det asynkrone kald inden
selve Web Formen opbygges og derved undgå at blokere nogle request tråde. Det fungerer, men det er uhyre bøvlet at kode.
I ASP.NET 2.0 er det bygget asynkron afvikling direkte ind på Web Formen. Vi har således den normale programmeringsmodel med adgang til
samtlige kontroller på siden både for den del af siden der afvikles synkront og den der afvikles asynkront.
Er det besværet værd?
Web Formen herunder indeholder 2 tekstfelter, en knap samt 2 labels. Når knappen trykkes så kaldes en webservices, der kan vende teksten,
2 gange - en for hver tekst. Denne webservice har indbygget en 2 sekunders forsinkelse.
I en ASP.NET 1.1 implementation, hvor 3 forskellige
scenarier var implementeret:
- Synkrone kald til de 2 webservices (således at et request teoretisk tager 4 sekunder)
- Asynkrone kald til de 2 webservices (således at et request teoretisk tager 2 sekunder)
- Ved brug af PreRequestHandlerExexuteAsync (også 2 sekunder i teorien, men uden at låse en request tråd i disse 2 sekunder
Disse 3 implementationer blev udsat for en stress test, der simulerede 100 samtidige brugere - testen kørte i 60 sekunder. Antallet af
gennemførte requests var:
- 160 stk (gennemsnitlig requesttid 35 sekunder)
- 346 stk (gennemsnitlig requesttid 17 sekunder)
- 1867 stk (gennemsnitlig requesttid 3 sekunder!)
Resultatet er godt nok på ASP.NET 1.1, men resultatet for en ASP.NET 2.0 implementation vil nok ikke afvige meget fra dette.
Resultatet taler sit tydelige sprog - hvis du bekymrer dig om throughput i din Web Forms applikation og den benytter sig af anden
længerevarende processesering så er det tid for "Asynkron afvikling af ASP.NET 2.0 sider"!
Et eksempel
Her er implementationen af ovenstående eksempel i ASP.NET 2.0. For at gøre en side klar til asynkron afvikling sætter vi
async="true" i page direktivet på formen:
<%@ Page Language="VB" AutoEventWireup="false" Async="true" ... %>
I koden skal vi registrerer et par metoder: BeginAsynData og et EndAsyncData metoder for at siden bliver afviklet asynkront. Alle de "normale"
AsP.NET events afvikles på request tråden frem til PreRender eventet. Herefter kaldes den registrerede BeginAsyncData metode hvorefter request
tråden frigives. Når EndAsyncData kaldes så tildeles siden igen en request tråd og selve renderingen og de sidste events afvikles.
Den asynkrone del af siden skal kun afvikles hvis knappen trykkes, derfor registreres den asynkrone del i Button1_Click. Selve
afviklingen af de asynkrone webservices igangsættes i BeginGetAsyncData Da der er brug for 2 asynkrone webservice request, er der lavet
en hjælpe klasse (CombineMultipleAsyncResults), der kan forene de 2 asynkrone resultat objekter til et asynkront resultat objekt.
Når begge de 2 asynkrone webservices afsluttes kaldes EndGetAsyncData her aflæses resultatet fra de 2 webservices
Partial Class OrdVender03TekstViaForm_aspx
Dim _proxy As New WebServices.OrdvenderWebService
Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim bh As BeginEventHandler = AddressOf BeginGetAsyncData
Dim eh As EndEventHandler = AddressOf EndGetAsyncData
AddOnPreRenderCompleteAsync(bh, eh)
End Sub
Private Function BeginGetAsyncData(ByVal src As Object, _
ByVal args As System.EventArgs, _
ByVal cb As AsyncCallback, ByVal state As Object) _
As System.IAsyncResult
Dim async(1) As System.IAsyncResult
Dim asyncBoth As New CombineMultipleAsyncResults(Nothing, cb, async)
async(0) = _proxy.BeginLangsomOrdVender( _
txtInput1.Text, AddressOf asyncBoth.CheckIfCompleted, Nothing)
async(1) = _proxy.BeginLangsomOrdVender( _
txtInput2.Text, AddressOf asyncBoth.CheckIfCompleted, Nothing)
Return asyncBoth
End Function
Private Sub EndGetAsyncData(ByVal ar As IAsyncResult)
lblResultat1.Text = _
_proxy.EndLangsomOrdVender(CType(ar, CombineMultipleAsyncResults).Results(0))
lblResultat2.Text = _
_proxy.EndLangsomOrdVender(CType(ar, CombineMultipleAsyncResults).Results(1))
End Sub
End Class
Public Class CombineMultipleAsyncResults
Implements System.IAsyncResult
Private _state As Object
Private _results() As System.IAsyncResult
Private _callback As AsyncCallback
Public Property Results() As System.IAsyncResult()
Get
Return _results
End Get
Private Set(ByVal value As System.IAsyncResult())
_results = value
End Set
End Property
Public Sub New(ByVal state As Object, ByVal callback As AsyncCallback, _
ByVal ParamArray results() As System.IAsyncResult)
_state = state
_callback = callback
_results = results
End Sub
Public ReadOnly Property AsyncState() As Object _
Implements System.IAsyncResult.AsyncState
Get
Return _state
End Get
End Property
Public ReadOnly Property AsyncWaitHandle() As System.Threading.WaitHandle _
Implements System.IAsyncResult.AsyncWaitHandle
Get
Return _results(0).AsyncWaitHandle
End Get
End Property
Public ReadOnly Property CompletedSynchronously() As Boolean _
Implements System.IAsyncResult.CompletedSynchronously
Get
For Each res As System.IAsyncResult In _results
If Not res.CompletedSynchronously Then
Return False
End If
Next
Return True
End Get
End Property
Public ReadOnly Property IsCompleted() As Boolean _
Implements System.IAsyncResult.IsCompleted
Get
For Each res As System.IAsyncResult In _results
If Not res.IsCompleted Then
Return False
End If
Next
Return True
End Get
End Property
Public Sub CheckIfCompleted(ByVal asyncRes As System.IAsyncResult)
If (IsCompleted()) Then
_callback.Invoke(Me)
End If
End Sub
End Class
|