C# 在ASP.NETMVCWeb应用程序中,Task.Run被认为是错误的做法吗? 背景

C# 在ASP.NETMVCWeb应用程序中,Task.Run被认为是错误的做法吗? 背景,c#,asp.net,asp.net-mvc,async-await,task-parallel-library,C#,Asp.net,Asp.net Mvc,Async Await,Task Parallel Library,我们目前正在开发一个web应用程序,它依赖于ASP.NET MVC 5、Angular.JS 1.4、web API 2和Entity Framework 6。出于可伸缩性的原因,web应用程序的可伸缩性依赖于异步/等待模式。我们的域需要一些cpu密集型计算,这可能需要几秒钟(请参阅),他们使用ConfigureWait(错误) 例子 公共异步任务CalculateAsync(双参数1,双参数2) { //CalculateSync是同步的且cpu密集型(this.CalculateSync(p

我们目前正在开发一个web应用程序,它依赖于ASP.NET MVC 5、Angular.JS 1.4、web API 2和Entity Framework 6。出于可伸缩性的原因,web应用程序的可伸缩性依赖于异步/等待模式。我们的域需要一些cpu密集型计算,这可能需要几秒钟(请参阅),他们使用ConfigureWait(错误)

例子
公共异步任务CalculateAsync(双参数1,双参数2)
{
//CalculateSync是同步的且cpu密集型(this.CalculateSync(param1,param2))).ConfigureAwait(false);
}
问题
  • 使用Task.Run在异步Web API控制器中进行cpu绑定操作是否有任何性能优势
  • ConfigureAwait(false)真的避免了额外线程的创建吗
使用Task.Run在异步Web API控制器中进行cpu绑定操作是否有任何性能优势

想想真正发生了什么-
Task.Run()
在线程池上创建一个任务,您的
await
操作符将释放线程(我假设堆栈中的所有方法都在等待)。现在,您的线程回到池中,它可能会执行相同的任务!在这种情况下,显然没有性能增益。事实上,这是一个性能冲击。但是,如果线程拾取另一个任务(可能会发生这种情况),则另一个线程必须拾取
CalculateSync()
Task并从前者停止的位置继续。首先让原始线程执行
CalculateSync()
,不涉及任何任务,让另一个线程执行其他排队的任务,这样会更有意义

ConfigureAwait(false)真的避免了额外线程的创建吗

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

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

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

ConfigureAwait(false)真的避免了额外线程的创建吗

不,或者更恰当地说,这与此无关
ConfigureAwait
只是一个优化提示,只确定在线程跳转之间是否保持原始上下文。长话短说,它与线程的创建无关,至少在ASP.NET应用程序的上下文中,对性能的影响可以忽略不计

使用
Task.Run
在异步Web API控制器中执行cpu限制的操作是否有任何性能优势

没有。而且不管它是否受CPU限制。

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

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

ConfigureAwait(false)真的避免了额外线程的创建吗


它不会以这样或那样的方式影响线程的创建。它所做的只是配置是否在捕获的<代码>同步化上下文< /代码>上恢复。

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

在您的例子中,类似于
这个。CalculateSync(param1,param2)
是非异步方法,所以要异步调用它,您必须使用Task.Run


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

我认为Task.Run不是在创建线程,而是在使用线程池。啊,您完全正确!谢谢你的更正。啊,非常感谢你的详细解释!因此,最好只使用Task.FromResult从异步方法“CalculateAsync”调用同步方法“CalculateAsync”,并阻止@Fabe:No的线程。只需完全删除
CalculateAsync
,然后改用
CalculateSync
。@Stephen Cleary:但如果我必须提供异步方法签名,也就是说,由于接口的原因,我应该使用Task.FromResult来提供它吗?我想到的场景是,接口的某些实现是真正的异步实现,而有些则不是。@Fabe只是想澄清一下。。使用
任务时不会创建(或“生成”)线程。请在不使用
长运行
标志的情况下运行
。@i3arnon:非常感谢!这个标志对我来说是新的,我必须在MSDN中查找它。如果可以的话,我想你和克里斯·普拉特都会回答说这是最有帮助的;-)非常感谢!与Chris Pratt的问题相同:所以最好只使用Task.FromResult调用同步
public async Task CalculateAsync(double param1, double param2)
{
    // CalculateSync is synchronous and cpu-intensive (<10s)
    await Task.Run(() => this.CalculateSync(param1, param2))).ConfigureAwait(false);
}