Running an asynchronous operation triggered by an ASP.NET web page request
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
}
Solution 1:
If you are running webforms set Ansync = "true" in your .aspx page where you are making the request. <%@ Page Language="C#" Async="true" ... %>
Solution 2:
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);
}
}
Solution 3:
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.
Solution 4:
You can work around this limitation quite easily and without even setting Async to true.
public void Start()
{
new Task(() =>
{
backgroundThread.RunWorkerAsync();
}).Start();
}
Solution 5:
If you get this error when calling web service asynchronously, make sure adding the Async='true' attribute as instructed by the exception message?
top of the page < Page Language='VB' Async='true' AutoEventWireup='false' CodeFile='mynewpage.aspx.vb' Inherits='mynewpage' %>