2

I have a page that I have added an async task to, using Page.RegisterAsyncTask. If the page is accessed normally, eg by navigating to "/foo.aspx", then everything works as expected. The app has some rather complicated routing, and in some circumstances the page is created from a handler that calls BuildManager.CreateInstanceFromVirtualPath("~/foo.aspx", typeof(Page)) as IHttpHandler and then calls ProcessRequest on the resultant handler. It seems like the page is not waiting for the registered task to complete before completing.

How can I make the page wait for the async task to complete in this scenario?

Reproducing sample:

TestPage.aspx:

<%@ Page Language="C#" AutoEventWireup="true" Inherits="WebApplication1.TestPage" Async="true" AsyncTimeout="60" MasterPageFile="~/Site.Master" %>

<asp:Content ContentPlaceHolderID="MainContent" runat="server">
    <asp:Button Text="Do Stuff" OnClick="Button1_Click" ID="Button1" runat="server" />
    <asp:Literal ID="ResultsLiteral" runat="server" />

    <script runat="server">
        protected void Button1_Click(object sender, EventArgs e)
        {
            RegisterAsyncTask(new PageAsyncTask(DoStuff));
        }

        async System.Threading.Tasks.Task DoStuff()
        {
            await System.Threading.Tasks.Task.Delay(5);
            ResultsLiteral.Text = "Done";
        }
    </script>
</asp:Content>

TestHandler.cs

using System.Web;
using System.Web.Compilation;
using System.Web.Routing;
using System.Web.UI;

namespace WebApplication1
{
    public class TestHandler : IHttpHandler
    {
        public bool IsReusable
        {
            get { return false; }
        }

        public void ProcessRequest(HttpContext context)
        {
            GetHttpHandler().ProcessRequest(context);
        }

        IHttpHandler GetHttpHandler(HttpContext context)
        {
            return BuildManager.CreateInstanceFromVirtualPath("~/TestPage.aspx", typeof(Page)) as IHttpHandler;
        }
    }

    public class RouteHandler : IRouteHandler
    {
        public IHttpHandler GetHttpHandler(RequestContext context)
        {
            return new TestHandler();
        }
    }
}

Routing (add in RouteConfig.cs):

public static void RegisterRoutes(RouteCollection routes)
{
    routes.Add(new Route("TestHandler", new RouteHandler()));
}

Steps to reproduce:

  1. Navigate to TestPage.aspx, click button & message appears as expected
  2. Navigate to /TestHandler, you should see the same page, however if you click the button the message doesn't appear.
Jon
  • 16,212
  • 8
  • 50
  • 62

1 Answers1

5

You're invoking the Page synchronously. You need to invoke it asynchronously, otherwise async APIs like Page.RegisterAsyncTask won't behave properly. Try something like this instead:

public class TestHandler : IHttpAsyncHandler
{
    private IHttpAsyncHandler _handler;

    public bool IsReusable
    {
        get { return false; }
    }

    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback callback, object state)
    {
        _handler = GetHttpHandler(context);
        return _handler.BeginProcessRequest(context, callback, state);
    }

    public void EndProcessRequest(IAsyncResult asyncResult)
    {
        _handler.EndProcessRequest(asyncResult);
    }

    public void ProcessRequest(HttpContext context)
    {
        throw new NotSupportedException();
    }

    IHttpAsyncHandler GetHttpHandler(HttpContext context)
    {
        return BuildManager.CreateInstanceFromVirtualPath("~/TestPage.aspx", typeof(Page)) as IHttpAsyncHandler;
    }
}
Levi
  • 32,628
  • 3
  • 87
  • 88
  • perfect, thanks! I thought it would be something like that, but for some reason I didn't think Page implemented IHttpAsyncHandler so I didn't think that would work.... – Jon Jul 01 '14 at 05:38