为啥异步控制器方法像同步方法一样执行?
Posted
技术标签:
【中文标题】为啥异步控制器方法像同步方法一样执行?【英文标题】:Why async controller method is executing just like a sync method?为什么异步控制器方法像同步方法一样执行? 【发布时间】:2019-01-24 12:07:35 【问题描述】:我创建了示例 .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 在其中一个 cmets 中提到的,IIS 以某种方式限制或阻止请求。当我以 SelfHosted 身份启动 WebApi 时,它开始按预期工作:
-
在向 ASYNC 方法发出一堆请求后,“isresponsible”方法的执行时间非常快,约为 0.02 秒。
在向 SYNC 方法发出一堆请求后,“isresponsible”方法的执行时间非常慢,大约为 35 秒。
【问题讨论】:
httpClient.GetAsync(url).Result
.Result 让它在那里等待响应。 Asnyc 只是意味着它不会阻塞线程,这非常适合调用事物而不阻塞 UI(例如),但并不意味着它会转到下一行代码并继续运行。如果您想发出多个请求然后继续,那么您需要为每个请求创建一个线程。
ASP.NET Core 没有 SynchronizationContext...获取此链接了解大多数信息...blog.stephencleary.com/2017/03/…
也许问题出在客户端?在发出请求之前尝试在客户端使用System.Net.ServicePointManager.DefaultConnectionLimit = 100; System.Net.ServicePointManager.Expect100Continue = false; System.Net.ServicePointManager.UseNagleAlgorithm = true;
。
The operation has timed out at System.Net.HttpWebRequest.GetResponse() while sending large number of requests to a host的可能重复
@Archer 这就是我想要的 - 等待服务器响应并测量响应时间。首先,我使用请求长时间运行的方法对我的服务器进行 DDOS,然后检查它将如何为其他请求提供服务
【参考方案1】:
您似乎不了解异步。它不会使响应返回更快。在操作正在执行的所有操作(异步与否)完成之前,无法返回响应。如果有的话,异步实际上更慢,即使只是轻微的,因为异步处理涉及额外的开销,而同步处理不是必需的。
异步所做 所做的可能是允许为请求提供服务的活动线程返回到池中以服务其他请求。换句话说,异步是关于规模,而不是性能。只有当您的服务器收到大量请求时,您才会看到好处。然后,当传入的请求通常已排队同步时,您将处理来自某些异步任务的额外请求,从而将其线程丧失为原因。此外,不能保证线程将被释放。如果异步任务立即或接近立即完成,线程将被暂停,就像同步一样。
编辑
您还应该意识到 IIS Express 是单线程的。因此,它不是性能调优的好指标。如果您同时运行 1000 个请求,则有 999 个会立即排队。然后,您没有做任何异步工作 - 只是返回一个已完成的任务。因此,线程永远不会被释放,因此在这种情况下同步和异步之间实际上没有区别。因此,您可以确定处理 999 个请求的队列需要多长时间(加上最后的状态检查)。如果您执行以下操作,您可能会更幸运地找出差异:
await Task.Delay(500);
而不仅仅是返回Task.FromResult
。这样,线程上就有实际的空闲时间,可以让它返回到池中。
【讨论】:
对不起,我的问题可能不是很清楚。请查看我的代码中的操作顺序(以我的理解): 1. 对“异步”方法执行大量请求。所有的请求都开始工作,直到 Task.Delay()。然后他们每个人都应该快速解除其线程的阻塞(因为等待)。 2. 对“isresponsible”方法做SYNC请求来测量,它将执行多快。第 1 步没有阻塞请求。为什么第 2 步的请求(只有一个是同步的)执行这么长时间? 如果所有执行步骤 1 请求的线程都返回到池中,则步骤 2 中的请求应该执行得很快,因为 ThreadPool 中有空闲池。 正如我所说,IIS Express 是单线程的。这很可能是您的问题,尤其是当您在 Visual Studio 之外运行应用程序时它可以正常工作。 克里斯,你为什么决定我在 IIS Express 中运行我的应用程序?在我的问题中,我写了关于 IIS 10,而不是 Express。这两个应用程序都不是从 Visual Studio 执行的。【参考方案2】:IIS 以某种方式限制或阻止请求(如其中一个 cmets 中所述)。当我以 SelfHosted 身份启动 WebApi 时,它开始按预期工作:
isresponsible
方法在向 ASYNC 方法发出一堆请求后的执行时间非常快,大约为 0.02 秒。
isresponsible
方法在对 SYNC 方法的一堆请求之后的执行时间很慢,大约 35 秒。
【讨论】:
【参考方案3】:我不确定这是否会产生任何重大改进,但您应该在服务器中的每个可等待对象上调用 ConfigureAwait(false)
,包括在 GetAsync
中。
它应该更好地利用线程。
【讨论】:
没有必要这样做,因为 ASP .NET Core 中没有 SyncContext以上是关于为啥异步控制器方法像同步方法一样执行?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 WCF 异步方法在同步时不会抛出 FaultException?