-1

I've created sample .NET Core WebApi application to test how async methods can increase the throughput. App is hosted on IIS 10.

Here is a code of my controller:

[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" };
}

there are methods: Get() - to get result synchronously GetAsync() - to get result asynchronously. IsResponding() - to check that server can serve requests

Then I created sample console app, which creates 100 requests to sync and async method (no waiting for result) of the controller. Then I call method IsResponding() to check whether server is available.

Console app code is:

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

where {apiMethod} is "sync" or "async", depending on user input.

In both cases server is not responding for a long time (about 40 sec). I expexted that in async case server should continue serving requests fast, but it doesn't.

UPDATE 1: I've changed client code like this:

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

and calling IsResponding() method executing for a very long time.

UPDATE 2 Yes, I know how async methods work. Yes, I know how to use HttpClient. It's just a sample to prove theory.

UPDATE 3 As it mentioned by StuartLC in one of the comments, IIS somehow throtling or blocking requests. When I started my WebApi as SelfHosted it started working as expected:

  1. Executing time of "isresponsible" method after bunch of requests to ASYNC method is very fast, at about 0.02 sec.
  2. Executing time of "isresponsible" method after bunch of requests to SYNC method is very slow, at about 35 sec.
  • 2
    `httpClient.GetAsync(url).Result` <-- `.Result` makes it wait right there for the response. Asnyc just means it's not blocking the thread, which is perfect for calling things and not blocking a UI (for example), but doesn't mean it goes to the next line of code and continues running. If you wanted to make multiple requests and then continue then you need to create a thread for each request. – Reinstate Monica Cellio Jan 24 '19 at 12:18
  • ASP.NET Core does not have a SynchronizationContext...got to this link for most info...http://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html – Amin Sahranavard Jan 24 '19 at 12:20
  • Perhaps problem is on client side? Try to use `System.Net.ServicePointManager.DefaultConnectionLimit = 100; System.Net.ServicePointManager.Expect100Continue = false; System.Net.ServicePointManager.UseNagleAlgorithm = true;` on client-side prior to make requests. – Woldemar89 Jan 24 '19 at 12:30
  • Possible duplicate of [The operation has timed out at System.Net.HttpWebRequest.GetResponse() while sending large number of requests to a host](https://stackoverflow.com/questions/48785681/the-operation-has-timed-out-at-system-net-httpwebrequest-getresponse-while-sen) – mjwills Jan 24 '19 at 12:32
  • @Archer that's what I want to - waiting for server to response and measure the response time. First I'm DDOSing my server with requesting long-running methods and then check how it will serve other requests – Vadim Mingazhev Jan 24 '19 at 12:51
  • Are you running iis on desktop licensced windows? Threads aside, iis on home and professional versions of windows throttles the number of concurrent requests. Switching to an owin self host avoids the throttle. https://serverfault.com/a/800518/52326 – StuartLC Jan 24 '19 at 12:56
  • @StuartLC Win10 Enterprise, Licensed – Vadim Mingazhev Jan 24 '19 at 13:05
  • @AminSahranavard link states: *This means that blocking on asynchronous code won’t cause a deadlock. You can use Task.GetAwaiter().GetResult() (or Task.Wait or Task.Result) without fear of deadlock. However, you shouldn’t*. So don't call `.Result` it's blocking. – Erik Philips Jan 24 '19 at 13:09
  • You're also using HttpClient incorrectly. [HttpClient has been designed to be re-used for multiple calls. Even across multiple threads. My advice is to keep an instance of HttpClient for the lifetime of your application](https://stackoverflow.com/a/22561368/209259). No need to spin up more than 1. – Erik Philips Jan 24 '19 at 13:14
  • @ErikPhilips there is no blocking of asynchronous code when I make request to async method. Why server stops responding? About client - yes, I know this, and just for sample there is two ways of using HttpClient as you can see above. – Vadim Mingazhev Jan 24 '19 at 13:14
  • @VadimMingazhev I'm sorry but your comment is incorrect. [Task.Result - Accessing the property's get accessor blocks the calling thread until the asynchronous operation is complete;](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.result?view=netframework-4.7.2). – Erik Philips Jan 24 '19 at 13:18
  • @ErikPhilips yes, you're right, I'm blocking calling thread at line "httpClient.GetAsync($"http://localhost:50001/api/values/isresponding").Result.Content;", because I need to know, how much time this operation will consume. At server side I'm not blocking anything when calling method "async". And I'd like to clear up why my server is responding long at this line. – Vadim Mingazhev Jan 24 '19 at 13:23
  • @VadimMingazhev `return this.GetValues().Result;` is also blocking. Basically your code is non-async. – Erik Philips Jan 24 '19 at 13:23
  • "return this.GetValues().Result;" - is a special case, it used in method "sync". I'm comparing two methods, "sync" and "async". So there are two cases - when I call sync method and async method. Calling async method should not cause server irresponsiveness, at theory, but it does – Vadim Mingazhev Jan 24 '19 at 13:27
  • @StuartLC pls put your comment as the answer - selfhosted application works as expected! – Vadim Mingazhev Jan 25 '19 at 05:46
  • @VadimMingazhev As per the other comments, the solution is a combination of using a singleton HttpClient, [possible throttling](https://stackoverflow.com/questions/1361771/max-number-of-concurrent-httpwebrequests) of the number of client outbound requests to the same destination (localhost), and then the Nerfing done by MS on non-server variants of Windows on IIS server side. Can't find one for Win10, but here's a reference for [Win 8](https://weblogs.asp.net/owscott/windows-8-iis-8-concurrent-requests-limit). – StuartLC Jan 25 '19 at 06:25

3 Answers3

2

You don't seem to understand async. It doesn't make the response return faster. The response cannot be returned until everything the action is doing is complete, async or not. If anything, async is actually slower, if only slightly, because there's additional overhead involved in asynchronous processing not necessary for synchronous processing.

What async does do is potentially allow the active thread servicing the request to be returned to the pool to service other requests. In other words, async is about scale, not performance. You'll only see benefits when your server is slammed with requests. Then, when incoming requests would normally have been queued sync, you'll process additional requests from some of the async tasks forfeiting their threads to the cause. Additionally, there is no guarantee that the thread will be freed at all. If the async task completes immediately or near immediately, the thread will be held, just as with sync.

EDIT

You should also realize that IIS Express is single-threaded. As such, it's not a good guage for performance tuning. If you're running 1000 simultaneous requests, 999 are instantly queued. Then, you're not doing any asynchronous work - just returning a completed task. As such, the thread will never be released, so there is literally no difference between sync and async in this scenario. Therefore, you're down to just how long it takes to process through the queue of 999 requests (plus your status check at the end). You might have better luck at teasing out a difference if you do something like:

await Task.Delay(500);

Instead of just return Task.FromResult. That way, there's actual idle time on the thread that may allow it to be returned to the pool.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Sorry, maybe my question was not very clear. Please, look at operations sequence in my code (in my understanding): 1. Do a lot of requests to "async" method. All of the requests are starting to do job until Task.Delay(). Then each of them should fastly unblock its thread (because of await). 2. Do SYNC request to "isresponsible" method to measure, how fast it will execute. There were no blocking request at the step 1. Why do (this only one is synchronous) request from step 2 executes so long? – Vadim Mingazhev Jan 25 '19 at 05:20
  • If all of the threads, executing requests from step 1 are returned to pool, request from step 2 should execute fast, because of free pools in ThreadPool. – Vadim Mingazhev Jan 25 '19 at 05:27
  • As I said, IIS Express is single-threaded. That's most likely your issue here, especially since it works fine when you run the app outside of Visual Studio. – Chris Pratt Jan 25 '19 at 11:37
  • Chris, why did you decide that I run my App in IIS Express? In my question I wrote about IIS 10, not Express. Both of apps was executed not from Visual Studio. – Vadim Mingazhev Jan 27 '19 at 10:51
0

IIS somehow throtling or blocking requests (as mentioned in one of the comments). When I started my WebApi as SelfHosted it started working as expected:

Executing time of isresponsible method after bunch of requests to ASYNC method is very fast, at about 0.02 sec. Executing time of isresponsible method after bunch of requests to SYNC method is very slow, at about 35 sec.

-1

I'm not sure this will yield any major improvement, but you should call ConfigureAwait(false) on every awaitable in the server, including in GetAsync.

It should produce better use of the threads.

haimb
  • 381
  • 3
  • 4