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

Fab*_*abe 47 c# asp.net asp.net-mvc task-parallel-library async-await

背景

我们目前正在开发一个Web应用程序,它依赖于ASP .NET MVC 5,Angular.JS 1.4,Web API 2和Entity Framework 6.出于可伸缩性的原因,Web应用程序的重量依赖于async/await模式.我们的域需要一些cpu密集型计算,这可能需要几秒钟(<10s).在过去,一些团队成员使用Task.Run,​​以加快计算速度.因为在ASP .NET MVC或Web API控制器中启动一个额外的线程被认为是一种不好的做法(该线程不为IIS所知,所以不是在AppDomain Recycle上考虑=>参见Stephen Cleary的博客文章),他们使用了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);
}
Run Code Online (Sandbox Code Playgroud)

问题

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

Chr*_*att 54

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

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

ConfigureAwait(false)是否真的避免创建额外的线程?

不,或者更恰当的是,它不是那个.ConfigureAwait只是一个优化提示,只确定是否在线程跳转之间保持原始上下文.无论长短,都与线程的创建无关,至少在ASP.NET应用程序的上下文中,无论哪种方式都会对性能产生微不足道的影响.

  • @Fabe:不.只需完全删除`CalculateAsync`并使用`CalculateSync`代替. (3认同)
  • @Fabe只是为了澄清..在没有`LongRunning`标志的情况下使用`Task.Run`时没有创建(或"生成")线程. (2认同)

i3a*_*non 21

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

不.它是否受CPU限制并不重要.

Task.Run卸载工作到一个ThreadPool线程.web api请求已经使用了一个ThreadPool线程,所以你只是通过无缘无故地卸载到另一个线程来限制可伸缩性.

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

ConfigureAwait(false)是否真的避免创建额外的线程?

它不会以某种方式影响线程创建.它所做的就是配置是否继续捕获SynchronizationContext.

  • @Fabe直接调用同步方法.如果没有异步操作,则不需要开始任务. (4认同)
  • @Fabe CalculateAsync毫无意义,不应该存在.如果您需要将CalculateSync卸载到另一个线程,请让消费者自己使用Task.Run. (3认同)

sha*_*y__ 9

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

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

ConfigureAwait(false)是否真的避免创建额外的线程?

一点也不.它只是指出不应该在调用者的上下文上执行continuation.