【问题标题】:Should I wrap slow calls with Task.Run?我应该用 Task.Run 包装慢速调用吗?
【发布时间】:2014-08-09 08:53:51
【问题描述】:

我有一个 aync 的 ASP MVC 调用,因为它需要进行异步调用。我注意到有几个慢速同步调用(例如数据库访问)。该方法需要所有返回的数据都可用才能继续。

我想用 Task.Run 包装同步调用并等待所有这些调用。

包装慢速同步调用有意义吗?如果只有同步调用怎么办?

【问题讨论】:

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


    【解决方案1】:

    不要使用Task.Run 来并行化服务器端代码中的工作,除非您希望同时服务的客户端请求数量非常少。否则,您可能会加快单个请求的处理速度,但是当有很多用户时,您会损害 Web 应用程序的可伸缩性。

    【讨论】:

    • 使用 async/await 来允许保存当前线程而不是等待处理请求不是重点吗?如果需要并行任务,那么他应该使用 TPL,但使用 TAP 应该有助于扩展而不是阻碍它。
    • @RyanPeters,确实如此,但在服务器端上下文中,您通常只将 TAP 用于自然异步 API(查看 Stephen Cleary 的 "There Is No Thread")。这样,HTTP 请求的初始线程被释放到池中,然后可以服务另一个 HTTP 请求。但是,如果您使用Task.Run,则不会释放线程。至于包装单个同步调用,它根本没有意义——你不妨在当前线程上做。至于并行包装多个调用 - 你会损害可伸缩性。
    • 对于客户端 UI 应用,情况可能会有所不同,您不关心可伸缩性,而是关心保持 UI 线程的响应性。
    • @Noseratio - 你只会损害可伸缩性,因为你正在消耗和阻塞线程池线程,你没有占用额外的 CPU 资源,并且可能内存最少(线程池已经分配,所以它的堆栈已经分配)。虽然这可能确实是可扩展性的问题,但它确实取决于很多因素,以确定它是否是一个问题。我同意,从纯度的角度来看,如果可能的话,最好避免它。
    • @ErikFunkenbusch,是的,但只是消耗(使用Task.Run)和阻塞池线程可能会对可伸缩性产生非常严重的影响。还有臭名昭著的ThreadPool 口吃行为要记住,这里有一个related post 有一些研究。毕竟,为什么要加快单个客户端的请求处理速度(通过使用Task.Run 分叉阻塞调用),却因为没有空闲池线程而让其他客户端等待?
    【解决方案2】:

    为了尝试扩展@Noseratio,旋转线程以“加速”同步工作的规模非常糟糕。

    要记住的重要一点是,在 ASP.NET 中使用 Task.Run 是非常危险的,因为运行时不知道您排队了需要完成的工作,并且 IIS 可能会不时尝试回收您的应用程序,这将无意中导致工作突然中断。

    如果您使用的是 .NET Framework 4.5.2,则可以通过 HostingEnvironment.QueueBackgroundWorkItem 找到解决方案。您可以在Fire and Forget on ASP.NET 中阅读有关它的更多信息。如果没有,请阅读 Returning Early from ASP.NET Requests 以获取自定义实现。这两篇优秀的文章都出自@StephanCleary

    【讨论】:

    • 我不认为作者在这里谈论早点返回。您说的是“一劳永逸”或异步 void 方法,这可能会导致您正在谈论的问题。在这种情况下,他需要工作的结果,因此他必须等待它们完成,工作进程不会被回收。因此,以这种方式使用 Task.Run 没有“危险”,尽管正如 @Noseratio 提到的那样,它对可扩展性并不好。
    • 他正在等待这些任务,尽管他是awaiting,但 IIS 没有理由不尝试回收 eveb。我不是在谈论 async void 或 fire and forget,我在谈论在 ASP.NET 中一般使用Task.Run
    • 如果主动作线程没有完成并且正在等待,除非超时,否则它不会回收。如果你不是在谈论 Fire and Forget,你为什么要参考这两篇文章?
    • 我认为@ErikFunkenbusch 有一个观点。即,当执行点到达async Web API 方法中的第一个await 时,将释放主要操作线程(HTTP 请求已登陆以进行处理的初始线程)。但是,此时请求仍处于挂起状态(因为await),因此不会回收 IIS 进程/应用程序域。在内部,跟踪is done via AsyncManager。然而,像await Task.Run(DoWork) 这样的东西在 ASP.NET 中没有意义:它只是释放一个线程并获取另一个线程。
    【解决方案3】:

    如果任务是独立的(不依赖于其他任务的数据),并且可以独立执行,那么可以,一定要异步执行它们。如果它们是实体框架,并且是第 6 版,那么它提供了要调用的异步方法,并且您不必将它们包装在 Task.Run 中。

    即使任务不是独立的,您仍然可以对它们进行排序,以提高执行时的效率。

    然而,是否使用Task.Run 是一个重要的区别。 Task.Run 将使用 ThreadPool 线程,如果它正在执行同步操作,则会阻塞,因此会减少应用程序可用的 ThreadPool theads 的数量。如果您有很多用户,并且您正在执行许多任务,这可能是一个问题。

    尝试查找异步 api 而不是使用 Task.Run。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-22
      • 2014-03-07
      • 2012-06-14
      • 2023-03-06
      • 2011-09-27
      • 1970-01-01
      • 2014-10-18
      • 2018-09-14
      相关资源
      最近更新 更多