Asynchronous task in asp .net MVC 5

18,329

Solution 1

The usage of await means that you are waiting for the task to complete before continuing the code. If you want it to execute simultaneously with the other code you should change it like this

public class PlanDetailsController : Controller
{      
    [HttpPost]
    public async Task<ActionResult> Generate(PlanDetailsVM planDetailsVM) 
    {
       System.Diagnostics.Debug.WriteLine("controller 1");
       //calls business method to generate                       
       var task = Task.Run(() => _planDesignBS.GenerateBS(planDetailsVM));
       System.Diagnostics.Debug.WriteLine("controller 2");

        var quoteId = await task;
        return Json(quoteId , JsonRequestBehavior.AllowGet);
    }
}

However I wouldn't recommend this as it serves no purpose and can degrade performance via starving the ASP.NET thread pool of threads. Why do you want to do this anyway?

Solution 2

This line:

quoteId = await Task.Run(() => _planDesignBS.GenerateBS(planDetailsVM));

asynchronously waits for the call inside Task.Run to complete. That's why you're seeing "PlandesignBS" printed and only after that you see the call to controller 2.

If you want to issue a "fire and forget" style of execution, you actually don't want to wait at all.

You should definitely not use Task.Run inside ASP.NET at all, as it's dangerous. Instead, if you're on .NET 4.5.2 and above, you can use HostingEnvironment.QueueBackgroundWorkItem or if not, you can use BackgroundTaskManager by Stephan Cleary, which safely registers the background thread with the application pool, so your thread doesn't get terminated once a re-cycle is invoked:

[HttpPost]
public ActionResult Generate(PlanDetailsVM planDetailsVM) 
{
   System.Diagnostics.Debug.WriteLine("controller 1");

   HostingEnvironment.QueueBackgroundWorkItem(ct => _planDesignBS.GenerateBS(planDetailsVM));

   System.Diagnostics.Debug.WriteLine("controller 2");
   return Json(quoteId , JsonRequestBehavior.AllowGet);
}

As a side note - If you're calling Entity Framework, there's usually no need to involve extra threads. Version 6 and above exposes TAP async based API's which are Task returning which you can use at your disposal for doing these kind of IO based queries.

Solution 3

The problem is, that you simply cannot dispatch asynchronous code that fall out of the asp.net request pipeline per se.

The async feature in asp.net MVC only improves the internal handling of threads inside the IIS process. It does NOT enable you to finish and close the response to the client, before all execution is completed.

If you want to do a real "fire-and-forget" solution, you need more than that.

I recently had the requirement that mails be sent asynchronously after a form in a web application has been submit.

The only way I found to solve this problem was to use hangfire:

http://hangfire.io/

Share:
18,329
Anil kumar
Author by

Anil kumar

Updated on July 24, 2022

Comments

  • Anil kumar
    Anil kumar almost 2 years

    I am trying to use async, await and task to implement one of an asynchronous requirement in my web application. I have below code in my controller class. What I want is that after the flow reaches to printing "controller 1", the system starts the business method on a separate thread and without waiting for the response system should reach to print statement in controller and print "controller 2". But when I am executing the below code I am getting output pattern as this-

    controller 1
    PlandesignBS
    controller 2
    

    Whereas I am expecting this-

    controller 1
    controller 2 (Flow should reach here immediately rather than waiting 
                   for business methods to get executed First)
    

    Contoller Class Code-

    public class PlanDetailsController : Controller
    {      
        [HttpPost]
        public async Task<ActionResult> Generate(PlanDetailsVM planDetailsVM) 
        {
           System.Diagnostics.Debug.WriteLine("controller 1");
           //calls business method to generate                       
           quoteId = await Task.Run(() => _planDesignBS.GenerateBS(planDetailsVM));
           System.Diagnostics.Debug.WriteLine("controller 2");
    
            return Json(quoteId , JsonRequestBehavior.AllowGet);
        }
    }
    

    Business Method Code-

    public int GenerateBS(PandetailsDTO plandetails)
    {
      System.Diagnostics.Debug.WriteLine("PlandesignBS");
    
     //STEP 1-> call to dataLogic Layer to use Entity Framework
    
     // STEP 2-> call to a method (this method is part of dll used 
     //in our project and we don't have any control on it)
    
     return quoteId
    }
    

    EDIT: The reason I am trying to do this is that I want to use few http Posts requests from my client side. Suppose I call the Generate action method first from my client side and then don't want to wait for the response of it and at the same time want to invoke another http post.

    EDIT: Including EF code where I am getting execption when I am trying to follow few solutions.This method will be called from my GenerateBS method. I am getting exception on this line inside getSICCode method-

     DbContext.Database.ExecuteSqlCommand("spGET_SICCode @industryClasifID,@industrySubClasifID,@SICCode OUTPUT", industryClasifID, industrySubClasifID, SICCode); 
    

    EF Code-

     public class PlanDesignRepository : Repository<myobject>, IPlanDesignRepository
    {
        private IDbSet<myobject> _dbSet;
        private DbContext _dbContext;
    
        private IDbSet<myobject> DbSet
        {
            get
            {
                if (_dbSet == null)
                {
                    _dbSet = base.UnitOfWork.Context.Set<myobject>();
                }
                return _dbSet;
            }
        }
    
        private DbContext DbContext
        {
            get
            {
                if (_dbContext == null)
                {
                    _dbContext = base.UnitOfWork.Context;
                }
                return _dbContext;
            }
        }
     public string GetSICCode(IndustryClassficDO industryClassficDO)
        {
    
            SqlParameter industryClasifID = new SqlParameter("industryClasifID", SqlDbType.Int);
            industryClasifID.Value = industryClassficDO.IndustryClassificationId;
    
            SqlParameter industrySubClasifID = new SqlParameter("industrySubClasifID", SqlDbType.Int);
            industrySubClasifID.Value = industryClassficDO.IndustrySubClassificationId;
    
            SqlParameter SICCode = new SqlParameter("SICCode", SqlDbType.VarChar, 10);
            SICCode.Direction = ParameterDirection.Output;
    
            DbContext.Database.ExecuteSqlCommand("spGET_SICCode @industryClasifID,@industrySubClasifID,@SICCode OUTPUT", industryClasifID, industrySubClasifID, SICCode);            
    
    
            return (string)(SICCode.Value);
        }
      }
    

    EDIT: I have few http Post calls as shown below-

      AngularJS code for AJAX calls:
    
     $http({
     url: key_Url_Generate,
     method: Post,
     params: $scope.PlanDetails
            }).then(function (result) {
                     //Notify user for success or failure
                                      }