为什么异步控制器方法像同步方法一样执行?

时间:2019-01-24 12:07:36

标签: c# async-await asp.net-core-webapi

我已经创建了示例.NET Core WebApi应用程序,以测试异步方法如何增加吞吐量。应用托管在IIS 10上。

这是我的控制器的代码:

[HttpGet("sync")]
public IEnumerable<string> Get()
{
    return this.GetValues().Result;
}

[HttpGet("async")]
public async Task<IEnumerable<string>> GetAsync()
{
    return await this.GetValues();
}

[HttpGet("isresponding")]
public Task<bool> IsResponding()
{
    return Task.FromResult(true);
}

private async Task<IEnumerable<string>> GetValues()
{
    await Task.Delay(TimeSpan.FromSeconds(10)).ConfigureAwait(false);
    return new string[] { "value1", "value2" };
}

有一些方法: Get()-同步获取结果 GetAsync()-异步获取结果。 IsResponding()-检查服务器是否可以处理请求

然后,我创建了示例控制台应用程序,该应用程序创建了100个控制器的同步请求和异步方法请求(不等待结果)。然后,我调用方法IsResponding()来检查服务器是否可用。

控制台应用程序代码为:

      using (var httpClient = new HttpClient())
      {
        var methodUrl = $"http://localhost:50001/api/values/{apiMethod}";
        Console.WriteLine($"Executing {methodUrl}");
        //var result1 = httpClient.GetAsync($"http://localhost:50001/api/values/{apiMethod}").Result.Content.ReadAsStringAsync().Result;

        Parallel.For(0, 100, ((i, state) =>
        {
          httpClient.GetAsync(methodUrl);
        }));

        var sw = Stopwatch.StartNew();
        var isAlive = httpClient.GetAsync($"http://localhost:50001/api/values/isresponding").Result.Content;
        Console.WriteLine($"{sw.Elapsed.TotalSeconds} sec.");

        Console.ReadKey();
      }

其中{apiMethod}是“同步”还是“异步”,具体取决于用户输入。

在两种情况下,服务器长时间没有响应(大约40秒)。 我表示,在异步情况下,服务器应继续快速处理请求,但事实并非如此。

更新1: 我已经这样更改了客户端代码:

  Parallel.For(0, 10000, ((i, state) =>
  {
    var httpClient = new HttpClient();
    httpClient.GetAsync($"http://localhost:50001/api/values/{apiMethod}");
  }));

  using (var httpClient = new HttpClient())
  {
    var sw = Stopwatch.StartNew();
    // this method should evaluate fast when we called async version and should evaluate slowly when we called sync method (due to busy threads ThreadPool)
    var isAlive = httpClient.GetAsync($"http://localhost:50001/api/values/isresponding").Result.Content;
    Console.WriteLine($"{sw.Elapsed.TotalSeconds} sec.");
  }

并调用IsResponding()方法执行了很长时间。

更新2 是的,我知道异步方法如何工作。是的,我知道如何使用HttpClient。这只是证明理论的一个例子。

更新3 正如 StuartLC 在其中一项注释中提到的那样,IIS以某种方式限制或阻止了请求。当我以SelfHosted身份启动WebApi时,它开始按预期方式工作:

  1. 一堆对ASYNC方法的请求后,“负责任”方法的执行时间非常快,大约为 0.02秒
  2. 向SYNC方法发出一堆请求后,“负责任”方法的执行时间非常慢,大约需要 35秒

3 个答案:

答案 0 :(得分:1)

您似乎不了解异步。它不会使响应返回更快。直到动作完成的所有内容(异步或不异步)之前,都无法返回响应。如果有的话,异步实际上要更慢,甚至稍微慢一点,因为异步处理涉及额外的开销,而同步处理则不必要。

异步所做的可能会允许为请求提供服务的活动线程返回到池中,以服务其他请求。换句话说,异步是关于规模而不是性能。仅当服务器被请求猛击时,您才会看到好处。然后,当传入的请求通常已排队同步时,您将处理一些异步任务中的其他请求,从而使它们的线程丧失原因。此外,不能保证完全释放该线程。如果异步任务立即完成或立即完成,则线程将被保留,就像同步一样。

编辑

您还应该认识到IIS Express是单线程的。因此,这不是性能调整的好方法。如果您同时运行1000个请求,则会立即排队999个。然后,您无需执行任何异步工作-仅返回已完成的任务。这样,线程将永远不会被释放,因此在这种情况下,同步和异步之间实际上没有区别。因此,您可以确定处理999个请求队列所需的时间(最后还要进行状态检查)。如果您执行以下操作,则可能会更好地消除差异:

await Task.Delay(500);

代替返回Task.FromResult。这样,线程上就会有实际的空闲时间,这可能会使它返回到池中。

答案 1 :(得分:0)

IIS以某种方式限制或阻止请求(如注释之一中所述)。当我以SelfHosted身份启动WebApi时,它开始按预期方式工作:

在向ASYNC方法发出一堆请求后,isresponsible方法的执行时间非常快,大约为0.02秒。 一束向SYNC方法的请求后,isresponsible方法的执行时间非常慢,大约需要35秒。

答案 2 :(得分:-1)

我不确定这是否会带来任何重大改进,但是您应该在服务器中每个等待的位置(包括ConfigureAwait(false)中调用GetAsync

它应该更好地利用线程。