异步和等待:他们是坏? [英] async and await: are they bad?

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

问题描述

我们最近开发了基于SOA网站,但这个网站最后不得不可怕的负载和性能问题时,负荷下了。我张贴在这里有关这个问题一个问题:

<一个href=\"http://stackoverflow.com/questions/21369634/asp-net-website-becomes-unresponsive-under-load\">ASP.NET网站负载下没有响应

该网站是由这4个节点的集群,这是托管在其他4个节点的集群上,可以调用API网站上托管的API(WEB API)的网站。两者都是使用ASP.NET MVC 5和所有行动开发/方法是基于异步等待的方法。

在一些监控工具,如NewRelic的运行现场,调查几个转储文件和分析工作进程后,它变成了一个非常轻负载条件下(如16个并发用户),我们最后有大约900螺纹,利用100 CPU的%和填补了IIS线程队列!

即使我们设法在我们的团队引入缓存和性能修正堆许多开发人员部署站点到生产环境中认为,我们必须删除所有的异步方法和隐蔽两种API和网站,以普通的Web API和操作方法它只是返回一个行动的结果。

因为我的直觉是,我们还没有使用的异步方法我个人不愉快的做法正确,否则就意味着,微软已经推出了一项功能,基本上是相当的破坏性和不可用!

你知道吗,将其清除出在哪里以及如何异步方法应该/可以使用任何的参考?我们应该如何使用它们来避免这样的电视剧?例如根据我在MSDN上阅读我相信API层应该是异步,但该网站可能是一个正常的无异步ASP.NET MVC的网站。

更新:

下面是使所有的API通信异步方法。

 公共静态异步任务&LT; T&GT; GetApiResponse&LT; T&GT;(对象参数,串动,CTK的CancellationToken)
{
        使用(VAR的HttpClient =新的HttpClient())
        {
            httpClient.BaseAddress =新的URI(BaseApiAddress);            VAR格式=新JsonMediaTypeFormatter();            返回
                等待
                    httpClient.PostAsJsonAsync(动作,参数,CTK)
                        .ContinueWith(X =&GT; x.Result.Content.ReadAsAsync&LT; T&GT;(新[] {}格式)结果,CTK);
        }
    }

有没有做傻事用这种方法?需要注意的是,当我们转换所有的方法,以非异步方法我们得到了一个堆更好的性能。

下面是一个示例使用(我剪这是关系到验证code的其他位,记录等,这code是一个MVC操作方法的机构)。

在我们服务的包装:

 公共异步静态任务&LT;&IList的LT; D​​ownloadType&GT;&GT;类的getSupportedContentTypes()
{
  字符串的userAgent = Request.UserAgent;
  VAR参数=新的{Util.AppKey,Util.StoreId,QueryParameters =新的{=的UserAgent的userAgent}};
  VAR taskResponse =等待Util.GetApiResponse&LT; ApiResponse&LT; SearchResponse&LT; ProductItem&GT;&GT;&GT;(
                    参数,
                    API /内容/ ContentTypeSummary
                    默认情况下(的CancellationToken));
                    返回task.Data.Groups.Select(X =&GT; x.DownloadType())了ToList();
 }

和在行动:

 公共异步任务&LT;&的ActionResult GT; DownloadTypes()
    {
        IList的&LT; D​​ownloadType&GT; supportedTypes =等待ContentService.GetSupportedContentTypes();


解决方案

  

有没有做傻事用这种方法?需要注意的是,当我们转换
  所有的方法,以非异步方法我们得到了一个堆更好的表现。


我至少可以看到这两者是错在这里:

 公共静态异步任务&LT; T&GT; GetApiResponse&LT; T&GT;(对象参数,串动,CTK的CancellationToken)
{
        使用(VAR的HttpClient =新的HttpClient())
        {
            httpClient.BaseAddress =新的URI(BaseApiAddress);            VAR格式=新JsonMediaTypeFormatter();            返回
                等待
                    httpClient.PostAsJsonAsync(动作,参数,CTK)
                        .ContinueWith(X =&GT; x.Result.Content
                            .ReadAsAsync&LT; T&GT;(新[] {}格式)结果,CTK)。
        }
    }

首先,你传递给 ContinueWith 拉姆达堵:

  X =&GT; x.Result.Content.ReadAsAsync&LT; T&GT;(新[] {}格式)结果

这是等同于:

  X =&GT; {
    VAR任务= x.Result.Content.ReadAsAsync&LT; T&GT;(新[] {格式});
    task.Wait();
    返回task.Result;
};

因此,您阻止在其上的lambda是碰巧要执行的池线程。这有效地杀死了异步,自然的优点 ReadAsAsync API,并降低你的Web应用程序的可伸缩性。当心你的code其他地方是这样的。

其次,ASP.NET请求由服务器线程一个安装了一个特殊的背景下同步处理, AspNetSynchronizationContext 。当您使用等待的延续,延续回调将被张贴到相同的同步情况下,编译器生成的code将照顾这。 OTOH,当您使用 ContinueWith ,这不会自动发生。

因此​​,你需要明确提供正确的任务调度,除去保护。结果(这将返回一个任务)和展开的嵌套任务:

 回归
    等待
        httpClient.PostAsJsonAsync(动作,参数,CTK).ContinueWith(
            X =&GT; x.Result.Content.ReadAsAsync&LT; T&GT;(新[] {}格式)
            CTK,
            TaskContinuationOptions.None,
            。TaskScheduler.FromCurrentSynchronizationContext())展开();

这是说,你真的不需要这些额外的复杂性 ContinueWith 这里:

  VAR X =等待httpClient.PostAsJsonAsync(动作,参数,CTK);
返回等待x.Content.ReadAsAsync&LT; T&GT;(新[] {格式});

由Stephen Toub下面的文章是高度相关的:

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


  

如果我要调用异步方法在同步方面,其中使用的await
  是不可能的,什么是做的最好的方式?


您几乎永远不应该需要混合等待 ContinueWith ,你应该坚持使用等待。基本上,如果你使用异步,它必须是异步的一路

有关服务器端的ASP.NET MVC /网页API执行环境,它只是意味着控制器方法应该是异步并返回任务任务&LT;&GT; 检查的这个。 ASP.NET跟踪未决对于一个给定的HTTP请求的任务。该请求没有得到完成,直到所有任务已完成。

如果您的真正的需要调用一个异步在ASP.NET同步方法的方法,你可以使用 AsyncManager 注册待定任务。对于经典的ASP.NET,您可以使用<一个href=\"http://msdn.microsoft.com/en-us/library/system.web.ui.pageasynctask.aspx\"><$c$c>PageAsyncTask.

在最坏的情况下,你会打电话 task.Wait()和块,否则你的任务可能会继续特定的HTTP请求的范围之外。

有关客户端UI的应用程序,一些不同的情况有可能调用一个异步从同步方法的方法。例如,你可以使用 ContinueWith(动作,TaskScheduler.FromCurrentSynchronizationContext())和火从完成事件动作(如这个)。

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 website becomes unresponsive under load

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.

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!

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.

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!

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.

Update:

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.

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

And in the Action:

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

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

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

This is equivalent to:

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

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.

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.

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

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

The following article by Stephen Toub is highly relevant:

"Async Performance: Understanding the Costs of Async and 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?

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".

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.

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.

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

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).

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

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