【问题标题】:How to make ASP.Net MVC Controller Action Async如何使 ASP.Net MVC 控制器动作异步
【发布时间】:2016-01-14 04:29:18
【问题描述】:

我的愿望是制作一个异步执行长时间运行的 i/o 操作的 MVC 控制器 Action。我的目标是避免在这个长时间运行的方法完成时占用 ASP.Net 线程池中的线程。

Action 进行了两次调用。

第一次调用是对不包含任何异步方法的第 3 方 dll。此 dll 从专有数据库中读取并执行相当复杂的 CPU 绑定处理。最多可能需要几秒钟才能返回。

第二次调用将第一次调用的结果作为参数传递给使用 Entity Framework 的数据库查询。

简单来说,这是动作:

public async Task<ActionResult> MyActionAsync(arg1, arg2)
{
    var parameters = 3rdPartyComponent.TakesLongTime(arg1, arg2);

    Task<List<MyClass>> genericList = null;

    using (DbContexts.MyDbContext db = new DbContexts.MyDbContext())
        {
          genericList = await db.Database.SqlQuery<MyClass>(sql,parameters).ToListAsync();
        }

    return View("MyView", genericList);
}

我想让对 3rdPartyComponent 的调用可以等待。我最初的想法是这样做:

var parameters = await Task.Run(() => 3rdPartyComponent.TakesLongTime()).ConfigurateAwait(false);

但我读过几位主题专家明确指出,在 asp.net MVC Action 中使用 Task.Run() 会适得其反,永远不应该这样做。

3rdPartyComponent 是编译后代码的黑盒,无法更改以添加异步方法。

有什么方法可以让 3rdPartyComponent 的调用处于等待状态,这样整个 Action 就可以在不占用 asp.net 线程池中的线程的情况下运行?

【问题讨论】:

  • 在 asp.net MVC Action 中的Task.Run() 会适得其反,永远不应该这样做。这不是真的。在很多情况下,您可能想要这样做。我并不是说你应该一直这样做,但如果你想生成一个单独的线程,那为什么不呢?
  • 嘿汤姆,读一读这个:blog.stephencleary.com/2013/08/startnew-is-dangerous.html 并考虑你是否在优化方面为时过早......除非这是一种流量很大的方法,否则肯定有可能这是一个不必要的步骤。 (您可能最终会使用 Task.Factory.StartNew 以便您可以提供一个 TaskCreationOptions 以安排线程池之外的工作)
  • @spender,是的,这是一个大流量的方法,非常如此。
  • 在这种情况下,使用线程是个坏主意。但这并不意味着永远不应该这样做。 Web 应用程序中的线程应该用于同时执行多个任务或作为触发和忘记线程。产生一个线程来完成主进程可以做的相同工作(当主线程等待时)会适得其反。您为同一任务使用了两倍的线程。我相信这就是他的观点。
  • 缓存3rdPartyComponent.TakesLongTime()的结果会不会更简单?

标签: asp.net-mvc async-await


【解决方案1】:

第一次调用是对不包含任何异步方法的第 3 方 dll。此 dll 从专有数据库中读取,并执行相当复杂的 cpu-bound 处理。

我想等待 3rdPartyComponent 的调用。

有什么方法可以让 3rdPartyComponent 的调用处于等待状态,这样整个 Action 就可以在不占用 asp.net 线程池中的线程的情况下运行?

代码已经可以做到最好了。 Task.Run 和 Task.Factory.StartNew 都不会给您带来任何好处(即使您通过了 LongRunning 标志)。

由于 3rd-party dll 执行 CPU-bound 代码,它需要一个线程。即使可以修改,也只能使数据库访问(I/O 工作)异步;根据定义,CPU 工作是同步的,根据您的描述,听起来大部分时间都是受 CPU 限制的工作。

在 ASP.NET 上避免 Task.Run(以及更糟糕的 Task.Factory.StartNew)的全部意义在于它们会导致效率较低的行为。通过释放 ASP.NET 请求线程,您只是将一个未使用的线程返回到线程池; ASP.NET 请求线程没有什么神奇或特别之处。所以Task.Run通过切换到另一个线程池线程来释放一个线程池线程,Task.Factory.StartNew和LongRunning通过创建一个全新的线程来释放一个线程池线程,将工作安排到那个线程,然后拆除它最后的线程(此行为未记录或指定,但它是当前观察到的行为)。

所以你最终做的只是导致不必要的线程切换(在StartNew 的情况下,一个完整的额外线程)。 ASP.NET 旨在处理同步和异步工作;如果你有同步代码,最好的办法就是直接执行它,就像你的代码已经在做的那样。

【讨论】:

    【解决方案2】:

    是的,我认为在异步服务器方法中使用 Task.Run 是一种反模式,因为您安排的工作最终只会在线程池中重新排队,而这正是您刚刚来自的地方。 .. 净增益为零(减去在线程池中调度回调的开销)。

    我的第一个想法是开始工作

    Task.Factory.StartNew(action,TaskCreationOptions.LongRunning)
    

    TaskCreationOptions.LongRunning 暗示工作应该在新线程而不是线程池中完成。

    ...但是,如果这是一种流量大的方法,那么创建线程的速度会比清除线程的速度快,现在您已经失去了线程池的管理优势。

    如果流量真的像你说的那样高并且需要这种特殊处理,则可能需要进行某种限制和/或缓存......但这将是一个不同的问题......

    【讨论】:

    • ...是的,你真的可以这样拼写“缓存”!
    • 我谷歌“缓存”,它告诉我“显示缓存结果”:)
    • +1 表示 TaskCreationOptions.LongRunning 提示。但我认为这个问题的真正答案是“不要试图让这个动作异步,它会产生比它解决的问题更多的问题。”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-29
    • 1970-01-01
    • 1970-01-01
    • 2012-07-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多