23

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();
Community
  • 1
  • 1
Aref Karimi
  • 1,822
  • 4
  • 27
  • 46
  • 2
    Any examples on how the app is using async/await? When used with IO-bound operations like database calls and file operations, it should typically increase scalabiity, not decrease it. If it's spawning up unnecessary threads just so that every method can be marked async, that's probably a red flag and likely the cause of your problems. – Anthony Chu Feb 14 '14 at 05:10
  • 1
    Perhaps, your code wraps synchronous APIs with `Task.Run`, instead of using naturally async APIs? http://stackoverflow.com/q/21690385/1768303. Generally, using `Task.Run` on the server is a bad idea. – noseratio Feb 14 '14 at 05:30
  • Is it MVC 5? Or MVC 3 as your tag says? – Erik Funkenbusch Feb 14 '14 at 05:31
  • @ErikTheViking Sorry I amended the tag. – Aref Karimi Feb 14 '14 at 10:00
  • @AnthonyChu, pretty much every method and action method in both WEB and API (consumer and the service) are async! Today I created a totally sync version of these too and put them under load test. Surprisingly the async version of API is faster than sync one if I hit the API directly. But when I hit the web site using Apache benchmark tool the async version of API is three times slower! Do you think it's better to convert only the methods that communicate with the DB or ElasticSearch to async? And as Noseratio said use naturally async APIs? – Aref Karimi Feb 14 '14 at 10:03
  • @Aref - Are you calling multiple web api's simultaneously? If not, then making this async does not buy you anything. Async is useful when you need to do multiple simultaneous requests and wait for them all to finish (rather than sequentially, they can be done in parallel). Can you show us what your controller action methods look like? – Erik Funkenbusch Feb 14 '14 at 19:56
  • @ErikTheViking, I updated the post with a sample usage. We do not call the APIs simultaneously in the code but are not they called concurrently when we have multiple concurrent users hitting the same page? – Aref Karimi Feb 14 '14 at 20:08
  • Look, these nice keywords like async and await do not mean you don't have to track what exactly your app is doing, and how thread usage is occurring. You need to understand what is going on concurrently, where the app is waiting for things, and figure out if there is some kind of contention because of it. There is only so much we can do, because this is likely a complex interaction that only you can discover. – Erik Funkenbusch Feb 14 '14 at 20:46
  • Try this tool : http://www.red-gate.com/products/dotnet-development/ants-performance-profiler/ – Manish Jain Aug 31 '14 at 23:07

3 Answers3

37

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

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 1
    Good catch there. I was suspicious of that ContinueWith, but I couldn't put my finger on specifically what the problem would be. It seemed clear to me that something was preventing the thread from being returned to the threadpool. I like your solution of getting rid of the ContinueWith altogether. Much simpler. – Erik Funkenbusch Feb 15 '14 at 07:05
  • @ErikTheViking, perhaps there are a few fragments like this. Or something like calling this on a tight loop. I'm not sure a single blocking call might be able to cause a problem for 16 users. – noseratio Feb 15 '14 at 07:16
  • 1
    @Noseratio you are a genius! I made that change and my load test results improved massively! One question: 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? – Aref Karimi Feb 15 '14 at 11:34
  • @Aref, glad it helped. I've updated the answer to address your question on calling an `async` method from synchronous method. – noseratio Feb 15 '14 at 12:57
3

async and await should not create a large number of threads, particularly not with just 16 users. In fact, it should help you make better use of threads. The purpose of async and await in MVC is to actually give up the thread pool thread when it's busy processing IO bound tasks. This suggests to me that you are doing something silly somewhere, such as spawning threads and then waiting indefinitely.

Still, 900 threads is not really a lot, and if they're using 100% cpu, then they're not waiting.. they're chewing on something. It's this something that you should be looking into. You said you have used tools like NewRelic, well what did they point to as the source of this CPU usage? What methods?

If I were you, I would first prove that merely using async and await are not the cause of your problems. Simply create a simple site that mimics the behavior and then run the same tests on it.

Second, take a copy of your app, and start stripping stuff out and then running tests against it. See if you can track down where the problem is exactly.

Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
  • How can I find what is that silly thing? We have profiled almost the entire site and API and fixed every performance bottle neck. – Aref Karimi Feb 14 '14 at 10:09
  • @Aref: You should be looking for any use of `Task.Run`, `Task.Factory.StartNew`, or `Task.Start`; these should not be used in ASP.NET. Also, any use of `Parallel` or Parallel LINQ. – Stephen Cleary Feb 14 '14 at 13:10
  • @Aref - I gave you some techniques you can use, such as making a copy and stripping stuff out until the problem goes away. – Erik Funkenbusch Feb 14 '14 at 14:59
  • @ErikTheViking, Thanks Erik. The fact is that our API is based on a third party RESTful service. There is a method in the API which makes all the http calls to that third party service. We have built that API in an async way using HttpClient.PostJasonAsync etc. And since that core method is async, our entire API and WEB (which has the same method to communicate with the API) from head to toe are async! I wish we could make that method which does the communications async and leave the rest of the project non-async. Is it ever possible? – Aref Karimi Feb 14 '14 at 19:44
  • @Aref - how many restful service calls does a single web request make? – Erik Funkenbusch Feb 14 '14 at 19:53
  • @ErikTheViking I updated my question with the code of the method which makes all the http communications. – Aref Karimi Feb 14 '14 at 19:53
  • @ErikTheViking, maybe 3-5 calls. We have tried to cache the data as much as possible but still we get around 90% CPU usage , plus NewRelic shows that around 50% of requests are in the queue. – Aref Karimi Feb 14 '14 at 19:55
  • This smells to me like you are somewhere blocking or busy waiting, and this is serializing your calls or creating deadlocks. You really need to map out what is happening simultaneously, where you are waiting for things to complete, etc.. – Erik Funkenbusch Feb 14 '14 at 20:00
3

There is a lot of stuff to discuss.

First of all, async/await can help you naturally when your application has almost no business logic. I mean the point of async/await is to do not have many threads in sleep mode waiting for something, mostly some IO, e.g. database queries (and fetching). If your application does huge business logic using cpu for 100%, async/await does not help you.

The problem of 900 threads is that they are inefficient - if they run concurrently. The point is that it's better to have such number of "business" threads as you server has cores/processors. The reason is thread context switching, lock contention and so on. There is a lot of systems like LMAX distruptor pattern or Redis which process data in one thread (or one thread per core). It's just better as you do not have to handle locking.

How to reach described approach? Look at disruptor, queue incoming requests and processed them one by one instead of parallel.

Opposite approach, when there is almost no business logic, and many threads just waits for IO is good place where to put async/await into work.

How it mostly works: there is a thread which reads bytes from network - mostly only one. Once some some request arrive, this thread reads the data. There is also limited thread pool of workers which processes requests. The point of async is that once one processing thread is waiting for some thing, mostly io, db, the thread is returned in poll and can be used for another request. Once IO response is ready, some thread from pool is used to finish the processing. This is the way how you can use few threads to server thousand request in a second.

I would suggest that you should draw some picture how your site is working, what each thread does and how concurrently it works. Note that it's necessary to decide whether throughput or latency is important for you.

Martin Podval
  • 1,097
  • 1
  • 7
  • 16