Wednesday, 18 June 2014

Asynchronous http handlers

I had a synchronous Http Handler, let's call it 'GenericHandler'.
It was timing out in production, but not under development.
It seems that if you set debug="true" in your web.config it's designed not to time out (you wouldn't want timeouts while you're debugging now would you).

So to test this I turned off debugging and set a short timeout

e.g.

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="false">
    <httpRuntime executionTimeout="5"/>





I could then reproduce the exception:

Request timed out.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.Exception Details: System.Web.HttpException: Request timed out.Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace:
[HttpException (0x80004005): Request timed out.]

My solution was to wrap the synchronous handler inside an asynchronous one.

e.g.

<%@ WebHandler Language="C#" Class="GenericAsyncHandler" %>
 
using System;
using System.Web;
 
/// <remarks>
/// Borrowed heavily from http://msdn.microsoft.com/en-us/library/ms227433(v=vs.100).aspx
/// </remarks>
public class GenericAsyncHandler : IHttpAsyncHandler
{
    /// <summary>
    /// Shouldn't get any 'plain' requests
    /// </summary>
    /// <param name="context"></param>
    public void ProcessRequest(HttpContext context)
    {
      throw new NotImplementedException();
    }
 
    public bool IsReusable { get { return false; } }
 
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
      var async = new GenericAsyncHandlerResult(cb, context, extraData);
      async.StartAsyncWork();
      return async;
    }
 
    public void EndProcessRequest(IAsyncResult result) { }
  }
 
  internal class GenericAsyncHandlerResult : IAsyncResult
  {
    public AsyncCollectJobDiagnostics(AsyncCallback callback, HttpContext context, Object state)
    {
      _callback = callback;
      _context = context;
      _state = state;
      _completed = false;
    }
 
    public void StartAsyncWork()
    {
      ThreadPool.QueueUserWorkItem(StartAsyncTask, null);
    }
 
    private void StartAsyncTask(Object workItemState)
    {
      // Simply instantiate the current implementation and 'process' the request using it.
      new GenericHandler().ProcessRequest(_context);
 
      // we're done
      _completed = true;
      _callback(this);
    }
 
    private bool _completed;
    private readonly Object _state;
    private readonly AsyncCallback _callback;
    private readonly HttpContext _context;
 
    bool IAsyncResult.IsCompleted { get { return _completed; } }
    WaitHandle IAsyncResult.AsyncWaitHandle { get { return null; } }
    Object IAsyncResult.AsyncState { get { return _state; } }
    bool IAsyncResult.CompletedSynchronously { get { return false; } }
  }
}

No comments: