ASP.NET MVC2 Can AsyncController access HttpContext.Current?

1.1k views Asked by At

I'm trying to convert this method ExportTo3rdParty() to use an AsyncController:

public JsonResult SaveSalesInvoice(SalesInvoice invoice)
{
    SaveInvoiceToDatabase(invoice); // this is very quick 
    ExportTo3rdParty(invoice); // this is very slow and should be async
}

But the ExportTo3rdParty() method uses HttpContext.Current in multiple places (far too many to change - the original coder did not use enough dependency injection). For example it calls GetDefaultCurrency(). Will this still work when ExportTo3rdParty() is called through an AsyncController?

public Currency GetDefaultCurrency()
{
    Currency currency;
    string key = string.Format("DefaultCurrency_{0}", 
                                HttpContext.Current.User.Identity.Name);
    currency = HttpRuntime.Cache.Get(key) as Currency;
    if (currency == null)
    {
        currency = LookupDefaultCurrency();
        HttpRuntime.Cache[key] = currency;
    }
} 

I know that if I use Thread.Start that I can not access HttpContext.Current. But what about an AsyncController?

1

There are 1 answers

1
Shiv Kumar On

So let me ask you why you want to use an Async controller?

Do you think it's going to be faster? Just because something it slow doesn't mean you need to make it async. In fact you'll most likely find that the method is slower when run async due to thread management/context switching overheads.

From what little I can understand from the two methods you've shown. I'm guessing that ExportTo3Party can basically be done "out of band". That is by an external process. So what you should be doing is either use MSMQ to queue the job (this returns immediately) so it's non-blocking. And have some other process/application process the queued jobs. This other process could be a regular Console application that is kept running on the server (using Task Sheduler) and it simply processes jobs as soon as they arrive in the queue.

Or even simpler (if you've not used MSMQ), simply execute an external application (console app again) and not wait for the app to exit. So you can use System.Diagnostics.Process to start the process and don't WaitForExit.

Both of these alternatives are the right/better way to implement what I think ExportTo3rdParty is doing. Seeing that you're not waiting on a response from this method ot return it.

If I haven't convinced you yet, then:

From MSDN documentation

If an asynchronous action method calls a service that exposes methods by using the BeginMethod/EndMethod pattern, the callback method (that is, the method that is passed as the asynchronous callback parameter to the Begin method) might execute on a thread that is not under the control of ASP.NET. In that case, HttpContext.Current will be null, and the application might experience race conditions when it accesses members of the AsyncManager class such as Parameters. To make sure that you have access to the HttpContext.Current instance and to avoid the race condition, you can restore HttpContext.Current by calling Sync() from the callback method.

If the callback completes synchronously, the callback will be executed on a thread that is under the control of ASP.NET and the operations will be serialized so there are no concurrency issues. Calling Sync() from a thread that is already under the control of ASP.NET has undefined behavior.

The ActionCompleted method will always be called on a thread that is under the control of ASP.NET. Therefore, do not call fSync() from that method.

The callback that you pass to the Begin method might be called using a thread that is under the control of ASP.NET. Therefore, you must check for this condition before you call Sync(). If the operation completed synchronously (that is, if CompletedSynchronously is true), the callback is executing on the original thread and you do not have to call Sync(). If the operation completed asynchronously (that is, CompletedSynchronously is false), the callback is executing on a thread pool or I/O completion port thread and you must call Sync().

http://msdn.microsoft.com/en-us/library/ee728598.aspx