Car*_*pon 27 c# asp.net-mvc threadpool async-await asp.net-core
我在控制器中编写了几个操作方法来测试ASP.NET内核中同步和异步控制器操作之间的区别:
[Route("api/syncvasync")]
public class SyncVAsyncController : Controller
{
[HttpGet("sync")]
public IActionResult SyncGet()
{
Task.Delay(200).Wait();
return Ok(new { });
}
[HttpGet("async")]
public async Task<IActionResult> AsyncGet()
{
await Task.Delay(200);
return Ok(new { });
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,每秒请求数没有太大差异- 我希望异步终点每秒处理更多请求.我错过了什么吗?
Chr*_*att 65
是的,您错过了异步与速度无关的事实,并且与每秒请求的概念仅略微相关.
异步只做一件事,只做一件事.如果一个任务正在等待,而且任务不涉及CPU绑定工作,其结果是,在线程变为空闲,然后,该线程可能会被释放返回到池做其他的工作.
而已.简而言之,异步.异步的关键是更有效地利用资源.在你可能有线程被捆绑的情况下,只是坐在那里敲击他们的脚趾,等待一些I/O操作完成,他们可以改为负责其他工作.这导致您应该内化的两个非常重要的想法:
异步!=更快.事实上,异步更慢.异步操作涉及开销:上下文切换,数据在堆上和堆栈之间进行混洗等.这增加了额外的处理时间.即使在某些情况下我们只谈微秒,异步也总是比同等的同步过程慢.期.完全停止.
如果您的服务器处于负载状态,Async只会购买任何东西.只有在您的服务器受到压力时,async会给它一些急需的呼吸空间,而同步可能会让它瘫痪.一切都与规模有关.如果你的服务器只能处理少量的请求,你很可能永远不会看到同步的差异,就像我说的那样,你可能最终会使用更多资源,具有讽刺意味的是,因为涉及到开销.
这并不意味着你不应该使用异步.即使您的应用程序今天不受欢迎,但这并不意味着它不会更晚,并且在此时重新调整所有代码以支持异步将是一场噩梦.异步的性能成本通常可以忽略不计,如果你最终需要它,它将是一个节省生命的人.
UPDATE
在保持异步的性能成本可以忽略不计的情况下,有一些有用的提示,在大多数关于C#中的异步讨论中都不是很明显或者说得很清楚.
ConfigureAwait(false)
尽可能多地使用.
await DoSomethingAsync().ConfigureAwait(false);
Run Code Online (Sandbox Code Playgroud)
除了一些特定的异常之外,几乎所有的异步方法调用都应该遵循.ConfigureAwait(false)
告诉运行时您不需要在异步操作期间保留同步上下文.默认情况下,等待异步操作时,会创建一个对象以保留线程切换之间的线程本地.这占用了处理异步操作所涉及的大部分处理时间,并且在许多情况下完全没有必要.唯一真正重要的地方是诸如动作方法,UI线程等等 - 这些地方的信息与需要保留的线程相关联.您只需要保留一次此上下文,因此只要您的操作方法(例如,等待同步上下文完整的异步操作),该操作本身就可以执行其他异步操作,而不保留同步上下文.因此,您应该将await
操作方法等内容的使用限制在最低限度,而是尝试将多个异步操作组合到该操作方法可以调用的单个异步方法中.这将减少使用异步所涉及的开销.值得注意的是,这只是ASP.NET MVC中的操作问题.ASP.NET Core使用依赖注入模型而不是静态,因此没有线程本地需要关注.在别人对你可以使用ConfigureAwait(false)
在ASP.NET核心作用,但不是在ASP.NET MVC.实际上,如果您尝试,您将收到运行时错误.
您应尽可能减少需要保留的本地数量.在调用await之前初始化的变量将添加到堆中,并在任务完成后弹出后退.你声明的越多,进入堆的次数就越多.特别是大型对象图可以在这里造成严重破坏,因为大量的信息可以在堆上移动.有时这是不可避免的,但这是值得注意的.
如果可能,请删除async
/ await
keywords.请考虑以下示例:
public async Task DoSomethingAsync()
{
await DoSomethingElseAsync();
}
Run Code Online (Sandbox Code Playgroud)
在这里,DoSomethingElseAsync
返回一个Task
等待和解包的东西.然后,Task
创建一个new 来返回DoSometingAsync
.但是,如果相反,您将方法编写为:
public Task DoSomethingAsync()
{
return DoSomethingElseAsync();
}
Run Code Online (Sandbox Code Playgroud)
将Task
通过返回DoSomethingElseAsync
被直接返回DoSomethingAsync
.这减少了大量的开销.
请记住,这async
更多是关于扩展而不是性能。根据上面的性能测试,您不会看到应用程序扩展能力的改进。要正确测试扩展,您需要在与您的生产环境理想匹配的适当环境中进行负载测试。
您正在尝试仅基于 async 对性能改进进行微基准测试。您可能会看到性能明显下降(取决于代码/应用程序)。这是因为异步代码(上下文切换、状态机等)存在一些开销。也就是说,在 99% 的情况下,您需要编写代码以进行扩展(同样,取决于您的应用程序)——而不是担心在这里或那里花费的任何额外毫秒。在这种情况下,可以这么说,您不是只见树木不见森林。在测试async
可以为您做什么时,您应该真正关注负载测试而不是微基准测试。
归档时间: |
|
查看次数: |
12429 次 |
最近记录: |