async 和 await:它们是坏的吗? [英] async and await: are they bad?

查看:42
本文介绍了async 和 await:它们是坏的吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们最近开发了一个基于 SOA 的站点,但该站点在负载不足时最终出现了严重的负载和性能问题.我在这里发布了一个与此问题相关的问题:

We recently developed a site based on SOA but this site ended up having terrible load and performance issues when it went under load. I posted a question related this issue here:

ASP.NET 网站在负载下变得无响应

该站点由托管在 4 节点集群上的 API (WEB API) 站点和托管在另一个 4 节点集群上并调用 API 的网站组成.两者均使用 ASP.NET MVC 5 开发,所有操作/方法均基于 async-await 方法.

The site is made of an API (WEB API) site which is hosted on a 4-node cluster and a web site which is hosted on another 4-node cluster and makes calls to the API. Both are developed using ASP.NET MVC 5 and all actions/methods are based on async-await method.

在一些监控工具(例如 NewRelic)下运行站点,调查几个转储文件并分析工作进程后,结果发现在非常轻的负载(例如 16 个并发用户)下,我们最终拥有大约 900 个线程,使用了 100% CPU 并填满 IIS 线程队列!

After running the site under some monitoring tools such as NewRelic, investigating several dump files and profiling the worker process, it turned out that under a very light load (e.g. 16 concurrent users) we ended up having around 900 threads which utilized 100% of CPU and filled up the IIS thread queue!

即使我们通过引入大量缓存和性能修正设法将站点部署到生产环境中,我们团队中的许多开发人员认为我们必须删除所有异步方法并将 API 和网站转换为正常的 Web API 和仅返回 Action 结果的 Action 方法.

Even though we managed to deploy the site to the production environment by introducing heaps of caching and performance amendments many developers in our team believe that we have to remove all async methods and covert both API and the web site to normal Web API and Action methods which simply return an Action result.

我个人对这种方法不满意,因为我的直觉是我们没有正确使用异步方法,否则这意味着 Microsoft 引入了一个基本上具有破坏性和不可用的功能!

I personally am not happy with approach because my gut feeling is that we have not used the async methods properly otherwise it means that Microsoft has introduced a feature that basically is rather destructive and unusable!

您是否知道任何参考资料可以明确说明应该/可以在何处以及如何使用异步方法?我们应该如何使用它们来避免这样的戏剧性事件?例如根据我在 MSDN 上读到的内容,我相信 API 层应该是异步的,但该网站可能是一个普通的非异步 ASP.NET MVC 站点.

Do you know any reference that clears it out that where and how async methods should/can be used? How we should use them to avoid such dramas? e.g. Based on what I read on MSDN I believe the API layer should be async but the web site could be a normal no-async ASP.NET MVC site.

更新:

这是与 API 进行所有通信的异步方法.

Here is the async method that makes all the communications with the 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);
        }
    }

这个方法有什么傻事吗?请注意,当我们将所有方法转换为非异步方法时,我们获得了更好的性能.

Is there anything silly with this method? Note that when we converted all method to non-async methods we got a heaps better performance.

这是一个示例用法(我删除了与验证、日志记录等相关的代码的其他部分.此代码是 MVC 操作方法的主体).

Here is a sample usage (I've cut the other bits of the code which was related to validation, logging etc. This code is the body of a MVC action method).

在我们的服务包装器中:

In our service wrapper:

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();
 }

在行动中:

public async Task<ActionResult> DownloadTypes()
    {
        IList<DownloadType> supportedTypes = await ContentService.GetSupportedContentTypes();

推荐答案

这个方法有什么傻事吗?请注意,当我们转换所有方法到非异步方法,我们都获得了更好的性能.

Is there anything silly with this method? Note that when we converted all method to non-async methods we got a heaps better performance.

我可以看出这里至少有两个地方出了问题:

I can see at least two things going wrong here:

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

首先,您传递给 ContinueWith 的 lambda 是阻塞的:

Firstly, the lambda you're passing to ContinueWith is blocking:

x => x.Result.Content.ReadAsAsync<T>(new[] { formatter }).Result

这相当于:

x => { 
    var task = x.Result.Content.ReadAsAsync<T>(new[] { formatter });
    task.Wait();
    return task.Result;
};

因此,您阻塞了恰好在其上执行 lambda 的池线程.这有效地扼杀了自然异步 ReadAsAsync API 的优势,并减少了Web 应用程序的可扩展性.注意代码中其他类似的地方.

Thus, you're blocking a pool thread on which the lambda is happened to be executed. This effectively kills the advantage of the naturally asynchronous ReadAsAsync API and reduces the scalability of your web app. Watch out for other places like this in your code.

其次,ASP.NET 请求由安装了特殊同步上下文的服务器线程处理,AspNetSynchronizationContext.当您使用 await 进行延续时,延续回调将发布到相同的同步上下文,编译器生成的代码会处理这个.OTOH,当您使用 ContinueWith 时,这不会自动发生.

Secondly, an ASP.NET request is handled by a server thread with a special synchronization context installed on it, AspNetSynchronizationContext. When you use await for continuation, the continuation callback will be posted to the same synchronization context, the compiler-generated code will take care of this. OTOH, when you use ContinueWith, this doesn't happen automatically.

因此,您需要明确提供正确的任务调度程序,删除阻塞的 .Result(这将返回一个任务)和 Unwrap 嵌套任务:

Thus, you need to explicitly provide the correct task scheduler, remove the blocking .Result (this will return a task) and Unwrap the nested task:

return
    await
        httpClient.PostAsJsonAsync(action, parameters, ctk).ContinueWith(
            x => x.Result.Content.ReadAsAsync<T>(new[] { formatter }), 
            ctk,
            TaskContinuationOptions.None, 
            TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();

也就是说,您真的不需要 ContinueWith 的如此复杂性:

That said, you really don't need such added complexity of ContinueWith here:

var x = await httpClient.PostAsJsonAsync(action, parameters, ctk);
return await x.Content.ReadAsAsync<T>(new[] { formatter });

Stephen Toub 的以下文章非常相关:

The following article by Stephen Toub is highly relevant:

异步性能:了解异步和等待的成本".

如果我必须在同步上下文中调用异步方法,使用 await不可能,最好的方法是什么?

If I have to call an async method in a sync context, where using await is not possible, what is the best way of doing it?

您几乎不需要混合使用 awaitContinueWith,您应该坚持使用 await.基本上,如果你使用 async,它必须是异步的 一路".

You almost never should need to mix await and ContinueWith, you should stick with await. Basically, if you use async, it's got to be async "all the way".

对于服务器端 ASP.NET MVC/Web API 执行环境,它只是意味着控制器方法应该是 async 并返回一个 TaskTask<>,检查 这个.ASP.NET 跟踪给定 HTTP 请求的挂起任务.直到所有任务都完成后,请求才会完成.

For the server-side ASP.NET MVC / Web API execution environment, it simply means the controller method should be async and return a Task or Task<>, check this. ASP.NET keeps track of pending tasks for a given HTTP request. The request is not getting completed until all tasks have been completed.

如果您真的需要从 ASP.NET 中的同步方法调用 async 方法,您可以使用 AsyncManagerthis 注册待处理的任务.对于经典的 ASP.NET,您可以使用 PageAsyncTask.

If you really need to call an async method from a synchronous method in ASP.NET, you can use AsyncManager like this to register a pending task. For classic ASP.NET, you can use PageAsyncTask.

在最坏的情况下,您会调用 task.Wait() 并阻塞,否则您的任务可能会在该特定 HTTP 请求的边界之外继续执行.

At worst case, you'd call task.Wait() and block, because otherwise your task might continue outside the boundaries of that particular HTTP request.

对于客户端 UI 应用程序,从同步方法调用 async 方法可能会出现一些不同的情况.例如,您可以使用 ContinueWith(action, TaskScheduler.FromCurrentSynchronizationContext()) 并从 action 触发完成事件(如 这个).

For client side UI apps, some different scenarios are possible for calling an async method from synchronous method. For example, you can use ContinueWith(action, TaskScheduler.FromCurrentSynchronizationContext()) and fire an completion event from action (like this).

这篇关于async 和 await:它们是坏的吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆