c# - ASP.NET Core 同步和异步 Controller 操作之间没有太大区别

标签 c# asp.net-mvc asp.net-core async-await threadpool

我在 Controller 中写了几个 Action 方法来测试 之间的区别同步 异步 ASP.NET 核心中的 Controller 操作:

[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 { });
    }
}

然后我对同步端点进行负载测试:
enter image description here

...后跟异步端点:
enter image description here

如果我将延迟增加到 1000 毫秒,结果如下
enter image description here
enter image description here

如您所见, 没有太大区别。每秒请求数 - 我希望异步端点每秒处理更多请求。我错过了什么吗?

最佳答案

是的,您忽略了异步与速度无关的事实,它与每秒请求数的概念仅略有相关。

异步只做一件事。如果正在等待一个任务,并且该任务不涉及 CPU 密集型工作,因此该线程变为空闲状态,则该线程可能会被释放以返回到池中以执行其他工作。

就是这样。简而言之,异步。异步的目的是更有效地利用资源。在您可能有线程被捆绑的情况下,只是坐在那里轻敲他们的脚趾,等待某些 I/O 操作完成,他们可以转而处理其他工作。这导致您应该内化两个非常重要的想法:

  • 异步!= 更快。事实上,异步更慢。异步操作涉及开销:上下文切换、在堆上和堆外打乱数据等。这会增加额外的处理时间。即使我们在某些情况下只讨论微秒,异步总是比等效的同步过程慢。时期。句号。
  • 如果您的服务器处于负载状态,异步只会为您购买任何东西。只有在您的服务器受到压力时,异步才会给它一些急需的喘息空间,而同步可能会让它屈服。这一切都与规模有关。如果您的服务器只处理极少量的请求,您很可能永远不会看到同步的差异,就像我说的那样,具有讽刺意味的是,由于涉及的开销,您最终可能会使用更多资源。

  • 这并不意味着您不应该使用异步。即使您的应用程序今天不流行,也不意味着它不会在以后流行,并且在那时重新调整所有代码以支持异步将是一场噩梦。异步的性能成本通常可以忽略不计,如果您最终需要它,它将成为救命稻草。

    更新

    关于保持异步的性能成本可以忽略不计,有一些有用的技巧,在 C# 中的异步的大多数讨论中,这些技巧并不明显或没有很好地说明。
  • 使用 ConfigureAwait(false)尽可能多。
    await DoSomethingAsync().ConfigureAwait(false);
    

    除了一些特定的异常(exception),几乎每个异步方法调用都应该跟在 this 之后。 ConfigureAwait(false)告诉运行时您不需要在异步操作期间保留的同步上下文。默认情况下,当您等待异步操作时,会创建一个对象以保留线程切换之间的线程局部变量。这占用了处理异步操作所涉及的大部分处理时间,并且在许多情况下完全没有必要。唯一真正重要的地方是操作方法、UI 线程等 - 需要保留与线程相关的信息的地方。您只需要保留此上下文一次,例如,只要您的操作方法等待同步上下文完整的异步操作,该操作本身就可以执行其他不保留同步上下文的异步操作。因此,您应该限制 await 的使用。尽量减少操作方法之类的内容,而是尝试将多个异步操作分组到该操作方法可以调用的单个异步方法中。这将减少使用异步所涉及的开销。值得注意的是,这只是对 ASP.NET MVC 中的操作的关注。 ASP.NET Core 使用依赖注入(inject)模型而不是静态模型,因此不需要关注线程局部变量。在其他情况下,您可以使用 ConfigureAwait(false)在 ASP.NET Core 操作中,但不在 ASP.NET MVC 中。事实上,如果你尝试,你会得到一个运行时错误。
  • 应尽可能减少需要保留的本地人数量。您在调用 await 之前初始化的变量将添加到堆中,并在任务完成后弹出。您声明的越多,堆上的内容就越多。尤其是大型对象图在这里可能会造成严重破坏,因为要在堆上移入移出大量信息。有时这是不可避免的,但这是需要注意的。
  • 如果可能,请省略 async/await关键词。例如,考虑以下内容:
    public async Task DoSomethingAsync()
    {
        await DoSomethingElseAsync();
    }
    

    在这里,DoSomethingElseAsync返回 Task这是等待和解开的。然后,一个新的Task创建从 DoSometingAsync 返回.但是,如果相反,您将方法编写为:
    public Task DoSomethingAsync()
    {
        return DoSomethingElseAsync();
    }
    
    Task返回者 DoSomethingElseAsyncDoSomethingAsync直接返回.这减少了大量的开销。
  • 关于c# - ASP.NET Core 同步和异步 Controller 操作之间没有太大区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48015096/

    相关文章:

    c# - ILogger 接口(interface)未将所有内容记录到事件日志/核心 3.0

    c# - 如果我内联 HashSet 集合初始化,编译器是否知道只执行一次?

    c# - 无法添加 System.Web.dll 引用

    Java Spring MVC 局部 View

    C# ASP.NET Core Serilog 添加类名和方法来记录

    c# - 如何减少 ASP.NET Core 中的 cookie 大小(类似于使用引用模式)?

    c# - 在 C# 中使用组合键触发按钮单击事件

    c# - 发布目标中缺少 VS 2019 azure 函数

    c# - MVC "UrlData[0]"

    c# - 关于Entity Framework Context Lifetime的问题