Task.Run 在 ASP .NET MVC Web 应用程序中被认为是不好的做法吗?

Posted

技术标签:

【中文标题】Task.Run 在 ASP .NET MVC Web 应用程序中被认为是不好的做法吗?【英文标题】:Is Task.Run considered bad practice in an ASP .NET MVC Web Application? 【发布时间】:2016-02-19 06:17:54 【问题描述】:

背景

我们目前正在开发一个 Web 应用程序,它依赖于 ASP .NET MVC 5、Angular.JS 1.4、Web API 2 和 Entity Framework 6。出于可扩展性的原因,Web 应用程序的繁重性依赖于 async/await 模式。我们的域需要一些 CPU 密集型计算,这可能需要几秒钟( 请参阅 Stephen Cleary's blog post),他们使用了 ConfigureAwait(false)。

示例

public async Task CalculateAsync(double param1, double param2)

    // CalculateSync is synchronous and cpu-intensive (<10s)
    await Task.Run(() => this.CalculateSync(param1, param2))).ConfigureAwait(false);

问题

在异步 Web API 控制器中使用 Task.Run 进行 CPU 绑定操作是否有任何性能优势? ConfigureAwait(false) 真的可以避免创建额外的线程吗?

【问题讨论】:

Task.Run 没有创建线程,但我认为正在使用线程池。 啊,你完全正确!谢谢指正。 【参考方案1】:

在异步 Web API 控制器中使用 Task.Run 进行 cpu 绑定操作是否有任何性能优势?

想想到底发生了什么——Task.Run() 在线程池上创建了一个任务,而你的await 运算符将释放线程(我假设堆栈中的所有方法也在等待)。现在您的线程又回到了池中,它可能会执行相同的任务!在这种情况下,显然没有性能提升。实际上,性能受到影响。但是如果线程选择了另一个任务(这可能会发生),另一个线程将不得不选择CalculateSync() Task 并从前者停止的地方继续。让原始线程首先执行CalculateSync() 会更有意义,不涉及任何任务,并让另一个线程拥有其他排队的任务。

ConfigureAwait(false) 真的可以避免创建额外的线程吗?

一点也不。它只是指出不应在调用者的上下文中执行延续。

【讨论】:

非常感谢!与 Chris Pratt 和 i3arnon 相同的问题:所以最好只使用 Task.FromResult 从异步方法“CalculateAsync”调用同步方法“CalculateSync”并将线程阻塞 【参考方案2】:

在异步 Web API 控制器中使用 Task.Run 进行 cpu 绑定操作是否有任何性能优势?

零。没有。事实上,您通过产生一个新线程来阻碍性能。在 Web 应用程序的上下文中,生成线程与在“后台”中运行不同。这是由于 Web 请求的性质造成的。当有传入请求时,会从池中获取一个线程来为请求提供服务。使用异步允许线程在请求​​结束之前返回,if 并且仅当线程处于等待状态时,即空闲。产生一个线程来做工作,有效地使主线程空闲,允许它返回到池中,但你仍然有一个活动线程。将原始线程返回到池中此时没有任何作用。然后,当新线程完成它的工作时,你必须从池中请求一个主线程,最后返回响应。在所有工作完成之前无法返回响应,因此无论您使用 1 个线程还是 100 个线程,异步或同步,在所有工作完成之前都无法返回响应。因此,使用额外的线程只会增加开销。

ConfigureAwait(false) 真的可以避免创建额外的线程吗?

不,或者更恰当地说,不是这样。 ConfigureAwait 只是一个优化提示,只判断线程跳转之间是否保持原来的上下文。总而言之,它与线程的创建无关,至少在 ASP.NET 应用程序的上下文中,这两种方式的性能影响都可以忽略不计。

【讨论】:

非常感谢您的详细解释!所以最好只使用Task.FromResult从异步方法“CalculateAsync”调用同步方法“CalculateSync”并将线程阻塞 @Fabe:不。完全放弃CalculateAsync 并改用CalculateSync @Stephen Cleary:但是如果我必须提供异步方法签名,即由于接口,我应该使用 Task.FromResult 来提供它吗?我正在考虑接口的某些实现是真正的异步接口的场景,而有些则不是。 @Fabe 澄清一下.. 使用没有LongRunning 标志的Task.Run 时不会创建(或“生成”)线程。 @i3arnon:非常感谢!这个标志对我来说是新的,必须在 MSDN 中查找。如果可以的话,我希望你和 Chriss Pratts 的回答都是最有帮助的 ;-)【参考方案3】:

在异步 Web API 控制器中使用 Task.Run 进行 CPU 绑定操作是否有任何性能优势?

没有。而且它是否受 CPU 限制并不重要。

Task.Run 将工作卸载到ThreadPool 线程。 Web api 请求已经使用了ThreadPool 线程,因此您只是通过无缘无故地卸载到另一个线程来限制可伸缩性。

这在 UI 应用程序中很有用,其中 UI 线程是一个特殊的单线程。

ConfigureAwait(false) 真的可以避免创建额外的线程吗?

它不会以一种或另一种方式影响线程创建。它所做的只是配置是否在捕获的SynchronizationContext 上恢复。

【讨论】:

非常感谢!与 Chris Pratt 相同的问题:所以最好只使用 Task.FromResult 从异步方法“CalculateAsync”调用同步方法“CalculateSync”并将线程阻塞 @Fabe 直接调用同步方法即可。如果没有异步操作,则不需要任务开始。 @Fabe CalculateAsync 毫无意义,不应该存在。如果您需要将CalculateSync 卸载到不同的线程,让消费者自己使用Task.Run。 好的,但是如果消费者是托管在 IIS 中并由 Angular 调用的 Web api 控制器,我不应该在那里使用 Task.Run,​​对吗? @Fabe 否。当您有一个“特殊”线程(例如 UI 应用程序)时,您只想卸载到不同的线程。 Web API 没有。【参考方案4】:

您还需要考虑一件事。正如您告诉您的 api 正在执行 CPU 密集型任务,然后 async/await 帮助在另一个线程中运行该进程并释放您的应用程序池以进行另一个请求。意味着如果你正确使用 async/await,你的 web api 每秒可以处理更多的请求。

在您的情况下,this.CalculateSync(param1, param2) 是非异步方法,因此要异步调用此方法,您必须使用 Task.Run。

我建议删除 .ConfigureAwait(false),因为这实际上会降低您的性能。

【讨论】:

但是使用Task.FromResult不是更好吗? Task 可隐式转换为 Task,因此只需获取已完成的 Task(具有任何 T 和任何值)并使用它。您可以使用 Task.FromResult

以上是关于Task.Run 在 ASP .NET MVC Web 应用程序中被认为是不好的做法吗?的主要内容,如果未能解决你的问题,请参考以下文章

Task.Run 不运行方法?

Asp.net core web Api Fire and Forget BG task

在asp.net mvc中每天执行一次操作[重复]

Django模拟ASP.NET MVC 自动匹配路由(转载)

ASP.NET MVC API 或 WCF API

如何在 ASP.NET MVC 应用程序中管理和部署架构(使用 NHibernate)