9

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
                                  } 
8
  • How do you Dispose your DataContext? Commented Feb 22, 2015 at 19:01
  • What should I do to Dispose it? Commented Feb 22, 2015 at 19:07
  • You should call the Dispose method but I am not telling you to Dispose it I am asking where you Dispose it because the exception you report says that the Dispose method was called but some code tried to use the object after that. Commented Feb 22, 2015 at 19:18
  • I am not familiar with angular but this looks like a single request. If you do two separate $http(...) calls they should be sent simultaneously. Make sure you do not add the second call in the callback (then function) Commented Feb 22, 2015 at 19:47
  • yeah, but I don't know why is it not working for me. I will try whatever solves my issue whether the code change in client side code or the server side code. Commented Feb 22, 2015 at 19:51

4 Answers 4

8

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?

Sign up to request clarification or add additional context in comments.

9 Comments

When I am trying to implement your solution, as I am using Entity Framework in my code.. I am getting DBcontext error-"The operation cannot be completed because the DbContext has been disposed."
I don't know where you create or Dispose your context so I cannot help with that unless you provide this code. However I still need to ask what is your end goal. What are you hoping to achieve by doing this?
The reason I am trying to do this is that I want to use few http Posts requests from my client side. I want to 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. I will try to include the EF code as well in my question.
So you need to fire 2 AJAX requests with your client side JavaScript. Why do you need async code on the server?
Because my first AJAX request is not allowing my other AJAX request to send response unless it completes execution.
|
3

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.

6 Comments

I certainly am interested in using fire and forget style of execution but the problem I can't use HostingEnvironment.QueueBackgroundWorkItem as I am using low version of .NET and can't use any 3rd party solution. Is there any other way of doing this which is applicable for my case?
Yes, you can manually create your own implementation by implementing IRegisteredObject interface. Here is a simple walk through which can get you started. Also, you can look at the implementation of BackgroundTaskManager
I tried to use BackgroundTaskManager but got an EF related exception. I have included the details in Question. Please see if you can help on it.
What is the exception message?
Exception is "The operation cannot be completed because the DbContext has been disposed"
|
1

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/

1 Comment

He doesn't even want to fire and forget solution. After all he uses the result of the async computation in the action result. What he does is simply useless.
0

Try 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 quoteIdTask =  GenerateAsyncBs();

           System.Diagnostics.Debug.WriteLine("controller 2");

int id = await quoteIdTask;

            return Json(quoteId , JsonRequestBehavior.AllowGet);
        }
    }

    private async Task<int> GenerateBsAsync()
    {
      //do your planDetails logic here.
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.