Paul
Paul

Reputation: 276

Run task on background but return response to client in ASP MVC web application

I'm trying to implement a functionality where there is a stored procedure on SQL Server that has to be called from ASP MVC application and processed on the background (it might take long since it calls another stored procedure remotely to process an excel file stored on a server). But the response of the last HTTP request should be returned back to client so the UI will not be hanging waiting for processing.

I have tried so many different ways but UI is still not responding right away.

I tried BackgroundWorker but it's not allowing the main thread to response back to client until its done processing, also I tried:

Thread.QueueUserWorkItem(delegate { //method which performs stored procedure calls//
}); 

It still not returning response and HttpContext.Current not available in background thread.

Maybe there is a way to start background processing, pause it for letting main thread to return response to browser and then resume background thread to make all processing with stored procedure calls?

Am I missing something?

Could someone please give an idea how I can solve this problem? Would be much appreciated.

Upvotes: 13

Views: 17747

Answers (4)

Paul
Paul

Reputation: 276

What I ended up with and it works fine in my case. I'm not sure about efficiency, but it perfectly works. So, the code which makes calls to a stored procedure I put on a separate thread, so that the main thread is finished while the processing off background calls is happening on a separate thread and finishes successfully after some period of time. At the same time UI is available so user can make another request which will also be processed the same way. I tested three requests. One after another overlapping, meaning that while first request was being processed on the background I submitted another one and yet another one. UI was responding immediately and all the work was done.

// ...main thread is working here
    //put a call to stored procedure on a separate thread
    Thread t = new Thread(()=> {
       //call stored procedure which will run longer time since it calls another remote stored procedure and
       //waits until it's done processing
    });
    t.Start();

   // ...main thread continue to work here and finishes the request so it looks for user as the response is coming right away, all other stuff is being processed on that new thread so user even doesn't suspect

Upvotes: 9

Ross Bush
Ross Bush

Reputation: 15175

There is part1 and part2 article by Dino Esposito outlines a way to achieve your polling using a client side timer and controller actions. You would basically serialize access to a progress worker controller method that returns task status and completion data. However, it may be a little chatty if you are only going to be performing one or two long running processes.

Upvotes: 3

Brian R. Mullin
Brian R. Mullin

Reputation: 423

If the response to the client does not depend on the result of the background process (i.e. run the process in the background and don't have the UI waiting for it), then you could use Revalee (an open-source tool) to perform the background task.

The UI will request this route...

public class ForegroundController : Controller
{
  public ActionResult InitiateBackgroundTask()
  {
    // The absolute URL that will be requested on the callback.
    var callbackUri = new Uri("http://localhost/BackgroundTask/Callback");

    // The information that will be needed to initiate the background task.
    object state = "Any object";

    this.CallbackNowAsync(callbackUri, state)

    // ... your controller will now return a response to the browser
    return View();
  }
}

Background work will be executed in this controller...

public class BackgroundTaskController : Controller
{
  [AllowAnonymous]
  [HttpPost]
  [CallbackAction]
  public ActionResult Callback(Guid callbackId, object state)
  {
    // Perform the background work

    // ... insert your background task code here ...

    // Return a status code back to the Revalee Service.
    return new HttpStatusCodeResult(HttpStatusCode.OK);
  }
}

Revalee Project Site

Upvotes: 0

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149538

I shall quote Stephan Clearys great article:

When you use async on the server side (e.g., with ApiController), then you can treat each web request as an asynchronous operation. But when you yield, you only yield to the web server thread pool, not to the client. HTTP only allows a single response, so the response can only be sent when the request is fully complete.

Basically, this doesn't adhere to the HTTP protocol, where each request has only one response.

This can be achieved using multiple calls to the ASP.NET service, where a request returns a unique ID immediately, which the client can query multiple times for progress. You may look into SignalR for help with such an implementation:

What is SignalR and "real-time web" functionality? It's the ability to have your server-side code push content to the connected clients as it happens, in real-time.

Upvotes: 5

Related Questions