views:

4741

answers:

3

I have an asynchronous operation that for various reasons needs to be triggered using an HTTP call to an ASP.NET web page. When my page is requested, it should start this operation and immediately return an acknowledgment to the client.

This method is also exposed via a WCF web service, and it works perfectly.

On my first attempt, an exception was thrown, telling me:

Asynchronous operations are not allowed in this context.
Page starting an asynchronous operation has to have the Async
attribute set to true and an asynchronous operation can only be
started on a page prior to PreRenderComplete event.

So of course I added the Async="true" parameter to the @Page directive. Now, I'm not getting an error, but the page is blocking until the Asynchronous operation completes.

How do I get a true fire-and-forget page working?

Edit: Some code for more info. It's a bit more complicated than this, but I've tried to get the general idea in there.

public partial class SendMessagePage : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string message = Request.QueryString["Message"];
        string clientId = Request.QueryString["ClientId"];

        AsyncMessageSender sender = new AsyncMessageSender(clientId, message);
        sender.Start();

        Response.Write("Success");
    }
}

The AsyncMessageSender class:

public class AsyncMessageSender
{
    private BackgroundWorker backgroundWorker;
    private string client;
    private string msg;

    public AsyncMessageSender(string clientId, string message)
    {
        this.client = clientId;
        this.msg = message;

        // setup background thread to listen
        backgroundThread = new BackgroundWorker();
        backgroundThread.WorkerSupportsCancellation = true;
        backgroundThread.DoWork += new DoWorkEventHandler(backgroundThread_DoWork);
    }

    public void Start()
    {
        backgroundThread.RunWorkerAsync();
    }

    ...
    // after that it's pretty predictable
}
A: 

Hi,

I don't know if you tried:

http://www.asp.net/ajax/

and

http://www.asp.net/mvc/

Thoughts on Asynchronisity

I would read those before attempting anything of this sort..have done plenty of asynchronous calls, all under the Ajax.

Try searching Scott Gu's blog. I am sure its all you need.

Ric Tokyo
Thanks for the answer, but it's really not an Ajax thing. It's a straight HTTP GET request. The asynchronous call starts on the server rather than the client.
Damovisa
+7  A: 

If you don't care about returning anything to the user, you can just fire up either a separate thread, or for a quick and dirty approach, use a delegate and invoke it asynchrnously. If you don't care about notifying the user when the async task finishes, you can ignore the callback. Try putting a breakpoint at the end of the SomeVeryLongAction() method, and you'll see that it finishes running after the page has already been served up:

private delegate void DoStuff(); //delegate for the action

protected void Page_Load(object sender, EventArgs e)
{

}

protected void Button1_Click(object sender, EventArgs e)
{
    //create the delegate
    DoStuff myAction = new DoStuff(SomeVeryLongAction); 
    //invoke it asynchrnously, control passes to next statement
    myAction.BeginInvoke(null, null);
    Button1.Text = DateTime.Now.ToString();
}


private void SomeVeryLongAction()
{
    for (int i = 0; i < 100; i++)
    {
        //simulation of some VERY long job
        System.Threading.Thread.Sleep(100);
    }
}
Ilya Tchivilev
Thanks, BeginInvoke was the key - I'm not sure why I didn't try that one!
Damovisa
Danger though! Ignoring the callback will result in resources not being cleaned up. That's one reason I recommended a different approach. Jeffrey Richter talks about this in his CLR via C# book (I may not have the title exactly right).
Charlie Flowers
It is EndInvoke that must be called at some point to ensure cleanup. Very true. Good discussion:http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/d88d3f1e-f4aa-40c1-b1b6-e79e801f3909/http://msdn.microsoft.com/en-us/magazine/cc164036(printer).aspx
Ilya Tchivilev
In the code example above, where should EndInvoke be called?
Phil
+3  A: 

OK, here's the problem: the Async attribute is for the case where your page is going to call some long-running task that also blocks the thread, and then your page needs the output from that task in order to return info to the user. For example, if your page needed to call a web service, wait for its response, and then use the data from the response to render your page.

The reason you'd use the Async attribute is to avoid blocking the thread. This is important because ASP.NET applications use a thread pool to serve requests, and there are only a relatively small number of threads available. And if each call ties up the thread while waiting on the web service call, then soon you're going to hit enough concurrent users that users are going to have to wait until these web service calls complete. The Async attribute lets the thread return to the thread pool and serve other concurrent visitors to your web site, rather than forcing it to sit still doing nothing while waiting for the web service call to return.

The upshot for you is this: the Async attribute is designed for the case where you can't render the page until the asynchronous task completes, and that's why it doesn't render the page immediately.

You need to launch your own thread, and make it a daemon thread. I don't remember the exact syntax for that, but you can easily find it in the doc by searching the BCL doc for "daemon". This means the thread will keep your application from shutting down while it is alive, which is important because ASP.NET and IIS reserve the right to "recycle your process" when they deem it necessary, and if that happens while your thread is working, your task will be stopped. Making the thread daemon will prevent this (except for some possible rare edge cases ... you'll find out more when you find the documentation on this).

That daemon thread is where you will kick off these tasks. And after you've told the daemon thread to do the task, you can immediately render your page ... so the rendering of the page will happen immediately.

Even better than a daemon thread in your ASP.NET process, though, would be to implement a Windows Service for doing the task. Have your ASP.NET application communicate the task to be performed to the Service. No need for a daemon thread and no need to worry about your ASP.NET process being recycled. How do you tell the Service to do the task? Perhaps through WCF, or perhaps by inserting a record into a database table that the Service polls. Or a number of other ways.

EDIT: Here's another idea, which I have used before for this very same purpose. Write the info about your task into an MSMQ queue. Have another process (maybe even on another machine) pull from that queue and do the time-consuming task. The job of inserting into a Queue is optimized to return as quickly as possible, so your thread won't block while the data you put in the Queue is sent across the wire or anything like that. It is one of the fastest ways to make note of the fact that a task needs to be done without waiting for that task to execute.

Charlie Flowers
Great answer - thanks for that. I'm actually using MSMQ down the line a bit. All I really needed to do was launch another thread to do my work as you suggested. As you mentioned, the Async parameter in the @Page directive wasn't going to help me.
Damovisa
Good, glad it helped. BTW, be sure to see my comment on the other answer ... ignoring the EndInvoke side of things will cause a resource leak! And the only reliable way to be sure you can process the EndInvoke is to have a daemon thread. So you're back to that or MSMQ / Service.
Charlie Flowers
Thanks for that - I actually reused some code that did a similar thing and just didn't do anything on EndInvoke.
Damovisa