views:

148

answers:

1

Writing a Service that is running on IIS.

Basically looks like this:

void ProcessRequest(HttpContext context)
{
     <Init Stuff>
     <Access DB>   // This may potentially stall for DB access
     <Write Output to conext stream>
}

By stalling the thread in the <Access DB> section we are basically blocking one of the IIS service threads. So looking around a way to get around this is:

IAsyncResult BeginProcessRequest(HttpContext context,AsyncCallback cb,Object extraData)
{
    this.del = new AsyncTaskDelegate(ProcessRequest);
    this.del.BeginInvoke(context, cb, extraData);  
}
void EndProcessRequest(IAsyncResult result)
{
    this.del.EndInvoke(ar);
}

This looks like it creates another thread to invoke the ProcessRequest(). Thus we are still stalling on the <Access DB> but this time we are stalling using a thread that does not belong to IIS. To me this is nice and symmetrical and keeps the code clean and easy to read.

Talking to a colleague, he says this does not buy you anything as the thread is still stalled. I agree but counter that it is not an IIS thread so it does buy me something. BUT he claims that if we use BeginProcessRequest() to make sure that the only the <Access DB> is done async then we can buy a lot more as no threads will be stalled.

This is psedu code as I have not worked out the details:

void ProcessRequest(HttpContext context)
{ /* Do Nothing */ }
IAsyncResult BeginProcessRequest(HttpContext context,AsyncCallback cb,Object extraData)
{
     <Init Stuff>
     this.command = <Access DB>.getSQLCommand();
     this.command.BeginExecuteNonQuery(cb,extraData);   // Assume we want to wait for this to complete.
}
void EndProcessRequest(IAsyncResult result)
{
     this.command.EndExecuteNonQuery(result);
     <Write Output to conext stream>
}

I can see this being an advantage of this does not stall the thread that executes BeginExecuteNonQuery(). But this requires the underlying implementation to use select() to detect when the DB call actually has data waiting to be read. While the easier way to implement it would be to just stall the thread waiting for a response (in which case adding the extra granularity does not buy me anything).

So does anybody have any references to indicate what the better method actually is?
Or have notes on how the underlying implementation of BeginExecuteNonQuery() works?
Or just any general information that may help.

Thanks

Edit:

The SqlConnection class contains a thread pool.

Thus when you do BeginExecuteNonQuery() on a SqlCommand it actually does not need to create a thread. The request is sent and the main control thread will spin up a thread from the pool when data is returned from the SQL-Server. Thus option 3 above does not neadlesly waste threads or cause a thread to hang when doing the DB operation async.

A: 

The issue here is that, logically, you still require the thread to be stalled. Your process needs to accept the HttpContext in its ProcessRequest() method, do something (Db access), and write something back to the context.

The stateless nature of HTTP means that socket connections are opened, the request is made, the client awaits a response, and the connection is closed. You don't have an open connection just waiting around for your handler to write something back to the client. In your example above, where does EndProcessRequest() get its HttpContext object to write its output? If it's the same one that was passed in to BeginRequest, which it must be if you're trying to get your output to go back to that client, then the client is going to spend the entire time between request and response waiting - while maintaining an open connection - for the server to do its thing and send some output. That context and its related connection must be kept open as long as your separate thread is spinning, which means that you are, in fact, blocking the ASP.NET thread in the mean time.

You gain nothing from launching an additional thread unless you are launching more than one, and can guarantee that they will complete in a reasonable amount of time.

Also, this is unnecessary. In order for the thread exec time to be a problem, you would have try to process more requests "simultaneously" than the server can handle. If your "db access" method is taking too long, you'll wind up queuing your requests, which will eventually lead to timeouts. The solution here is to keep your response times (page render times and all db access, etc that rendering requires) as short as possible.

Summary:

  1. Don't fire off a single additional thread - this is only useful if you are running multiple threads

  2. You must make sure that any additional threads you initiate will complete within the alloted time for an HTTP request

  3. The calling ASP.NET thread will have to wait until a response is written back to the Http context, or until a timeout occurs. This may result in orphaned threads if you are launching new ones in the ProcessRequest method.

In short, don't do it. If you need to initiate processes from BeginRequest that are asynchronous and do not have to write output back to the client, consider writing a windows service to handle these requests. Log each request into a table, and have your service poll that table to see what needs to be done. Have your client poll that table to see when a result is ready (possibly via AJAX). This may not be appropriate for your application, but is one way to handle this kind of problem.

David Lively