Hello everyone,
I have a query that's probably quite common, but there's a part of how Threads are managed that I don't quite fully grasp, so i'm wondering what's the best way to solve my scenario.
The functional overview is quite simple: My site visitor wants to search, he enters a query, I forward that query to a 3rd party catalog that searches for me, and I return the results to the client.
The search works over AJAX, so I'm receiving the request on an ASHX handler, and returning the HTML that the
JS will then put into a div directly.
This 3rd party service is expected to take a while to respond (many seconds)
My question is how to handle the threads on the server to avoid a bottleneck.
The obviously simplest way is to have the ASHX open a WebRequest to the 3rd party catalog directly. The obvious problem is, i can only have 25 queries running simultaneously in that case (i'm picking 25 as the number of threads in my pool from now on. I know this can be changed but that's not the point.)
I want to release these threads so they can keep handling other requests while the searches I spawned are pending, so the first thing I'm doing is using an IHttpAsyncHandler
In the method where I call the 3rd party URL, i'm using HttpWebRequest.BeginGetResponse (with a callback), so that part is handled Async too.
My question is...
When I call BeginGetResponse, I can immediately free the thread that I'm in and return it to the pool.
However, when I do that, am I not spawning a new thread that is alive while the request goes on, and ends up calling my callback once it finishes?
If I AM spawing this new thread, doesn't this consume one thread from the pool too, and I'm actually getting a net effect of the same as going synchronous?
If I am NOT spawning a new thread, then how does my callback get called? Who is checking whether this async method I started is finished? (This is the main part of the puzzle that I don't quite grasp)
If a response from the 3rd party catalog takes forever, could I potentially have 1000 of these async WebRequest methods pending a response at the same time, and still be using no threads (or very few) from the 25-thread pool?
Or will I only be able to have 25 of these waiting for a response from the catalog, and the rest will be queued up by IIS?
Below I'm attaching the code that I wrote that shows what i'm doing.
This is obviously oversimplified. I left the "architectural" stuff, and removed all the things specific to my case.
Am I doing things right? Should I be using an Async HTTP Handler, AND calling BeginGetResponse?
Is there a better way?
Thank you very much for your answers!
Daniel
----------------------------------------------------------------------------------------------
Code:
<%@ WebHandler Language="VB" Class="SearchHttpHandler" %>
Option Strict On
Imports System.Threading
Imports System.IO
Imports System.Net
Public Class SearchHttpHandler
Implements IHttpAsyncHandler
Implements IReadOnlySessionState
'---------------------------------------------------------------------------------------
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
End Sub
'---------------------------------------------------------------------------------------
Function BeginProcessRequest(ByVal context As System.Web.HttpContext, ByVal cb As System.AsyncCallback, ByVal extraData As Object) As System.IAsyncResult Implements IHttpAsyncHandler.BeginProcessRequest
context.Response.Write("Started on thread: " & _
Threading.Thread.CurrentThread.ManagedThreadId & " - " & _
Threading.Thread.CurrentThread.IsThreadPoolThread.ToString & "<br />")
'Instantiate the AsyncResult object that'll give us a hold to the context and the callback
Dim theAsyncResult As New SearchAsyncResult(context, cb, extraData)
theAsyncResult.Search()
Return theAsyncResult
End Function
'---------------------------------------------------------------------------------------
Sub EndProcessRequest(ByVal result As System.IAsyncResult) Implements IHttpAsyncHandler.EndProcessRequest
Dim theObj As SearchAsyncResult = DirectCast(result, SearchAsyncResult)
theObj.Context.Response.Write("Ended on thread: " & _
Threading.Thread.CurrentThread.ManagedThreadId & " - " & _
Threading.Thread.CurrentThread.IsThreadPoolThread.ToString)
End Sub
'---------------------------------------------------------------------------------------
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property
End Class
'======================================================================================
'======================================================================================
Public Class SearchAsyncResult
Implements IAsyncResult
Public Context As HttpContext
Private mASPNETCallback As AsyncCallback
Private mExtraData As Object
Private mIsCompleted As Boolean
Private mAsyncWaitHandle As ManualResetEvent
Private mRequest As HttpWebRequest
Private mQuery As String
'-------------------------------------------------------------
Public Sub New(ByVal context As HttpContext, ByVal ASPNETCallback As AsyncCallback, ByVal ExtraData As Object)
Me.Context = context
mASPNETCallback = ASPNETCallback
mExtraData = ExtraData
End Sub
'-------------------------------------------------------------
Public Sub Search()
If Context.Request.QueryString("q") IsNot Nothing Then
mQuery = Context.Request.QueryString("q")
End If
'Return "No Results" if there is no query
If mQuery = "" Then
Context.Response.Write("No Results")
CompleteRequest()
Exit Sub
End If
'Otherwise, make an async call
Dim URL As String = "xxxxxxx?q=" & mQuery
mRequest = DirectCast(WebRequest.Create(URL), HttpWebRequest)
mRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"
mRequest.Method = "GET"
mRequest.BeginGetResponse(AddressOf SearchCallback, Nothing)
End Sub
'-------------------------------------------------------------
Private Sub SearchCallback(ByVal ar As IAsyncResult)
Dim WebResp As WebResponse = mRequest.EndGetResponse(ar)
Dim sReader As New StreamReader(WebResp.GetResponseStream())
Dim result As String = sReader.ReadToEnd()
Context.Response.Write(result)
CompleteRequest()
End Sub
'-------------------------------------------------------------
Private Sub CompleteRequest()
mIsCompleted = True
SyncLock Me
If mAsyncWaitHandle IsNot Nothing Then
mAsyncWaitHandle.Set()
End If
End SyncLock
If mASPNETCallback IsNot Nothing Then
mASPNETCallback(Me)
End If
End Sub
'---------------------------------------------------------------------
#Region "Implementation of IAsyncResult dummy members"
Public ReadOnly Property AsyncState() As Object Implements System.IAsyncResult.AsyncState
Get
Return mExtraData
End Get
End Property
Public ReadOnly Property AsyncWaitHandle() As System.Threading.WaitHandle Implements System.IAsyncResult.AsyncWaitHandle
Get
SyncLock Me
If mAsyncWaitHandle Is Nothing Then
mAsyncWaitHandle = New ManualResetEvent(False)
End If
Return mAsyncWaitHandle
End SyncLock
End Get
End Property
Public ReadOnly Property CompletedSynchronously() As Boolean Implements System.IAsyncResult.CompletedSynchronously
Get
Return False
End Get
End Property
Public ReadOnly Property IsCompleted() As Boolean Implements System.IAsyncResult.IsCompleted
Get
Return mIsCompleted
End Get
End Property
#End Region
'---------------------------------------------------------------------
End Class 'SearchAsyncResult