【问题标题】:Asynchronous task in asp .net MVC 5asp .net MVC 5中的异步任务
【发布时间】:2015-02-23 03:32:55
【问题描述】:

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

controller 1
PlandesignBS
controller 2

而我期待这个-

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

控制器类代码-

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 请求。假设我首先从客户端调用 Generate 操作方法,然后不想等待它的响应,同时又想调用另一个 http post。

编辑:包括 EF 代码,当我尝试遵循几个解决方案时,我正在执行该代码。此方法将从我的 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
                                  } 

【问题讨论】:

  • 你如何处理你的 DataContext?
  • 我应该如何处理它?
  • 你应该调用 Dispose 方法,但我不是告诉你 Dispose 它我是问你在哪里 Dispose 它,因为你报告的异常说 Dispose 方法被调用,但一些代码试图使用该对象之后。
  • 我不熟悉 angular 但这看起来像是一个单一的请求。如果您进行两个单独的 $http(...) 调用,它们应该同时发送。确保不要在回调(然后函数)中添加第二个调用
  • 是的,但我不知道为什么它对我不起作用。无论是客户端代码还是服务器端代码中的代码更改,我都会尝试解决我的问题。

标签: c# asp.net asynchronous asp.net-mvc-5 async-await


【解决方案1】:

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 线程池的线程饥饿而降低性能。你为什么要这样做?

【讨论】:

  • 当我尝试实现您的解决方案时,因为我在我的代码中使用实体框架。我收到 DBcontext 错误-“操作无法完成,因为 DbContext 已被释放。”跨度>
  • 我不知道您在哪里创建或处置您的上下文,所以除非您提供此代码,否则我无法提供帮助。但是,我仍然需要问您的最终目标是什么。您希望通过这样做实现什么目标?
  • 我尝试这样做的原因是我想使用来自客户端的少量 http Posts 请求。我想首先从客户端调用 Generate 操作方法,然后不想等待它的响应,同时又想调用另一个 http 帖子。我将尝试在我的问题中包含 EF 代码。
  • 因此您需要使用客户端 JavaScript 触发 2 个 AJAX 请求。为什么服务器上需要异步代码?
  • 因为我的第一个 AJAX 请求不允许我的其他 AJAX 请求发送响应,除非它完成执行。
【解决方案2】:

这一行:

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

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

如果你想发出“一劳永逸”的执行方式,你实际上不想等待。

你绝对不应该在 ASP.NET 中使用Task.Runas it's dangerous。相反,如果您使用的是 .NET 4.5.2 及更高版本,则可以使用 HostingEnvironment.QueueBackgroundWorkItem,否则,您可以使用 Stephan Cleary 的 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);
}

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

【讨论】:

  • 我当然有兴趣使用火而忘记执行方式,但问题是我无法使用 HostingEnvironment.QueueBackgroundWorkItem,因为我使用的是低版本的 .NET 并且无法使用任何第 3 方解决方案。还有其他适用于我的情况的方法吗?
  • 是的,您可以通过实现IRegisteredObject 接口手动创建自己的实现。 Here 是一个简单的步骤,可以帮助您入门。另外,你可以看看BackgroundTaskManager的实现
  • 我尝试使用 BackgroundTaskManager 但遇到了与 EF 相关的异常。我已在问题中包含详细信息。请看看你能不能帮忙。
  • 异常信息是什么?
  • 异常是“操作无法完成,因为 DbContext 已被释放”
【解决方案3】:

问题在于,您根本无法分派从 asp.net 请求管道本身脱离的异步代码。

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

如果您想做一个真正的“即发即弃”的解决方案,您需要的还不止这些。

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

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

http://hangfire.io/

【讨论】:

  • 他甚至不想开火而忘记解决方案。毕竟他在动作结果中使用了异步计算的结果。他的所作所为根本没用。
【解决方案4】:

试试这个:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-16
    • 1970-01-01
    • 2017-08-29
    相关资源
    最近更新 更多