asp .net MVC 5 中的异步任务

Asynchronous task in asp .net MVC 5

我正在尝试使用 async、await 和 task 来实现我的 Web 应用程序中的一个异步要求。我的控制器中有以下代码 class。我想要的是,在流程到达打印 "controller 1" 之后,系统在单独的线程上启动业务方法,而不等待响应系统应该到达控制器中的打印语句并打印 "controller 2"。但是当我执行下面的代码时,我得到的输出模式是 this-

controller 1
PlandesignBS
controller 2

而我期待这个-

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

控制器Class代码-

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);
    }
}

业务方法代码-

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
}

编辑:我尝试这样做的原因是我想使用来自客户端的少量 http Posts 请求。假设我首先从客户端调用生成操作方法,然后不想等待它的响应,同时想调用另一个 http post.

编辑:包括 EF 代码,当我尝试遵循少数 solutions.This 方法时,我会从我的 GenerateBS 方法中调用这些方法。我在 getSICCode 方法中的这一行出现异常-

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

EF代码-

 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);
    }
  }

编辑:我有几个 http Post 调用,如下所示-

  AngularJS code for AJAX calls:

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

这一行:

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

a同步等待 Task.Run 内的调用完成。这就是为什么您会看到 "PlandesignBS" 被打印出来,并且只有在那之后您才会看到对控制器 2 的调用。

如果您想执行 "fire and forget" 风格的执行,您实际上 根本不想 等待。

你根本不应该在 ASP.NET 中使用 Task.Run,Stephan Cleary 的 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,它安全地将后台线程注册到应用程序池,所以你的线程不会调用重新循环后终止:

[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);
}

附带说明 - 如果您正在调用 Entity Framework,通常不需要涉及额外的线程。版本 6 及更高版本公开了基于 API 的 TAP 异步,它们是 Task 返回的,您可以随意使用它来执行这些基于 IO 的查询。

await 的使用意味着您在继续代码之前等待任务完成。如果您希望它与其他代码同时执行,您应该像这样更改它

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);
    }
}

但是我不推荐这样做,因为它没有任何用处,并且会因 ASP.NET 线程池的线程不足而降低性能。你为什么要这么做?

试试这个:

 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.
    }

问题是,您根本无法调度掉落在 asp.net 请求管道本身之外的异步代码。

asp.netMVC 中的异步功能仅改进了 IIS 进程内部线程的内部处理。它不允许您在所有执行完成之前完成并关闭对客户端的响应。

如果你想做一个真正的 "fire-and-forget" 解决方案,你需要的不止于此。

我最近有一个要求,即在 Web 应用程序中提交表单后异步发送邮件。

我发现解决这个问题的唯一方法是使用 hangfire:

http://hangfire.io/