Are*_*ref 19 performance async-await asp.net-mvc-4
我们最近开发了一个基于SOA的站点,但是当它负载不足时,该站点最终出现了可怕的负载和性能问题.我在这里发布了一个与此相关的问题:
该网站由被一个4节点集群,其中托管另一个4节点集群上并且对API调用的web站点上托管的API(WEB API)位点组成.两者都是使用ASP.NET MVC 5开发的,所有操作/方法都基于async-await方法.
在一些监控工具(如NewRelic)下运行站点,调查几个转储文件并分析工作进程后,发现在非常轻的负载下(例如16个并发用户),我们最终拥有大约900个线程,这些线程利用了100%的CPU并填满了IIS线程队列!
尽管我们通过引入大量缓存和性能修正设法将网站部署到生产环境,但我们团队中的许多开发人员认为我们必须删除所有异步方法并将API和网站转换为普通的Web API和Action方法.只需返回一个Action结果.
我个人对方法不满意,因为我的直觉是我们没有正确使用异步方法,否则就意味着微软已经引入了一个基本上具有相当破坏性和无法使用的功能!
您是否知道有哪些参考文献可以清楚地知道应该/可以使用异步方法的位置和方式?我们应该如何使用它们来避免这种戏剧?例如,根据我在MSDN上阅读的内容,我认为API层应该是异步的,但网站可能是一个普通的无异步ASP.NET MVC站点.
更新:
以下是与API进行所有通信的异步方法.
public static async Task<T> GetApiResponse<T>(object parameters, string action, CancellationToken ctk)
{
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(BaseApiAddress);
var formatter = new JsonMediaTypeFormatter();
return
await
httpClient.PostAsJsonAsync(action, parameters, ctk)
.ContinueWith(x => x.Result.Content.ReadAsAsync<T>(new[] { formatter }).Result, ctk);
}
}
Run Code Online (Sandbox Code Playgroud)
这种方法有什么愚蠢的吗?请注意,当我们将所有方法转换为非异步方法时,我们获得了更好的性能.
这是一个示例用法(我已经删除了与验证,日志记录等相关的代码的其他位.此代码是MVC操作方法的主体).
在我们的服务包装中:
public async static Task<IList<DownloadType>> GetSupportedContentTypes()
{
string userAgent = Request.UserAgent;
var parameters = new { Util.AppKey, Util.StoreId, QueryParameters = new { UserAgent = userAgent } };
var taskResponse = await Util.GetApiResponse<ApiResponse<SearchResponse<ProductItem>>>(
parameters,
"api/Content/ContentTypeSummary",
default(CancellationToken));
return task.Data.Groups.Select(x => x.DownloadType()).ToList();
}
Run Code Online (Sandbox Code Playgroud)
并在行动中:
public async Task<ActionResult> DownloadTypes()
{
IList<DownloadType> supportedTypes = await ContentService.GetSupportedContentTypes();
Run Code Online (Sandbox Code Playgroud)
nos*_*tio 35
这种方法有什么愚蠢的吗?请注意,当我们将所有方法转换为非异步方法时,我们获得了更好的性能.
我可以看到至少有两件事出了问题:
public static async Task<T> GetApiResponse<T>(object parameters, string action, CancellationToken ctk)
{
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(BaseApiAddress);
var formatter = new JsonMediaTypeFormatter();
return
await
httpClient.PostAsJsonAsync(action, parameters, ctk)
.ContinueWith(x => x.Result.Content
.ReadAsAsync<T>(new[] { formatter }).Result, ctk);
}
}
Run Code Online (Sandbox Code Playgroud)
首先,你传递给的lambda ContinueWith是阻塞的:
x => x.Result.Content.ReadAsAsync<T>(new[] { formatter }).Result
Run Code Online (Sandbox Code Playgroud)
这相当于:
x => {
var task = x.Result.Content.ReadAsAsync<T>(new[] { formatter });
task.Wait();
return task.Result;
};
Run Code Online (Sandbox Code Playgroud)
因此,您正在阻止正在执行lambda的池线程.这有效地ReadAsAsync消除了自然异步API 的优势,并降低了Web应用程序的可伸缩性.在代码中注意这样的其他地方.
其次,ASP.NET请求由服务器线程处理,其上安装了特殊的同步上下文AspNetSynchronizationContext.当您使用awaitcontinuation时,continuation回调将被发布到同一个同步上下文,编译器生成的代码将处理此问题.OTOH,当你使用时ContinueWith,这不会自动发生.
因此,您需要显式提供正确的任务调度程序,删除阻塞.Result(这将返回任务)和Unwrap嵌套任务:
return
await
httpClient.PostAsJsonAsync(action, parameters, ctk).ContinueWith(
x => x.Result.Content.ReadAsAsync<T>(new[] { formatter }),
ctk,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();
Run Code Online (Sandbox Code Playgroud)
也就是说,你真的不需要ContinueWith这里增加的复杂性:
var x = await httpClient.PostAsJsonAsync(action, parameters, ctk);
return await x.Content.ReadAsAsync<T>(new[] { formatter });
Run Code Online (Sandbox Code Playgroud)
Stephen Toub的以下文章具有高度相关性:
如果我必须在同步上下文中调用异步方法,那么使用await是不可能的,这样做的最佳方法是什么?
你几乎永远不应该需要混合await和ContinueWith,你应该坚持下去await.基本上,如果你使用async,它必须"一直"异步.
对于服务器端的ASP.NET MVC/Web API执行环境,它只是意味着控制器方法应该async并返回一个Task或Task<>,检查它.ASP.NET会跟踪给定HTTP请求的待处理任务.在完成所有任务之前,请求尚未完成.
如果你真的需要调用一个async从ASP.NET同步方法的方法,你可以使用AsyncManager像这样注册一个未决任务.对于经典ASP.NET,您可以使用PageAsyncTask.
在最坏的情况下,您将调用task.Wait()并阻止,否则您的任务可能会继续超出该特定HTTP请求的边界.
对于客户端UI应用程序,可以使用某些不同的方案async从同步方法调用方法.例如,您可以使用ContinueWith(action, TaskScheduler.FromCurrentSynchronizationContext())和触发完成事件action(如此).
| 归档时间: |
|
| 查看次数: |
10594 次 |
| 最近记录: |