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
:
我正在尝试使用 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
: