7

I'm trying to understand the intricacies of async/await in C# and I/O completion ports in Windows, writing code to verify my assumptions along the way.

From what I understand, calling WebClient.DownloadStringTaskAsync(...) will make the current thread register an I/O operation with an I/O completion port (this is probably a little vague, I don't understand the details yet), it will create a Task<string> and it will continue executing code. At some point it will encounter an await for the given task. At that point, it will return from the current method (well, it will exit from some scope, I'm not sure whether this scope could be something other than a method - I should probably inspect the generated state machine to understand that part). Once the I/O operation completes, a thread will be grabbed from the thread pool, it will be passed the result of the I/O operation and it will execute the rest of the scope just mentioned.

I've tried verifying this behaviour, but only got so far as this C# code before something didn't seem right:

class Program
{
    static void Main(string[] args)
    {
        ThreadPool.SetMaxThreads(30, 30);
        Console.WriteLine("Connection limit is {0}", ServicePointManager.DefaultConnectionLimit);
        for (int i = 0; i < 30; i++)
        {
            FetchAsync(i);
        }

        Console.WriteLine("Done starting requests");
        Console.ReadKey();
    }

    private async static void FetchAsync(int num)
    {
        WebClient wc = new WebClient();
        string result = await wc.DownloadStringTaskAsync("http://localhost/slow/index/15");
        Console.WriteLine("Done #{0}", num);
    }
}

As you can see, I am using a WebClient to create 30 requests for a web page (which I know is slow and will take 15 seconds to respond). Running this code, I observe the following behaviour: After 15 seconds the first 10 requests complete. After another 15 seconds, another 10 requests complete and after yet another 15 seconds, the remaining requests complete. Thus, it would seem that there are only 10 outstanding requests at a time (using perfmon I've verified that the web application being called has 10 current requests). Why is that? I was expecting 30 concurrent requests, as I've set the maximum number of threads for the thread pool as per the code above.

This SO question lead me to believe that ServicePointManager might have something to do with it, but since the DefaultConnectionLimit is only 2 I suppose there is no connection.

I realise that this is a pretty long-winded description of a fairly simple question. I hope it will make it easier for someone to point out exactly where my assumptions are wrong.

I'm running this on a Windows 7, 64 bit machine.

Community
  • 1
  • 1
Rune
  • 8,340
  • 3
  • 34
  • 47
  • 1
    Are you running this on a Windows Server OS, or client OS (such as Win 7)? There used to be baked-in limitations for network activity in the client windows versions. –  Oct 27 '12 at 19:54
  • Thanks for commenting. I've updated the question: I'm running Windows 7 on a 64bit Thinkpad. Do you know if these limitations can be altered (or at least inspected)? – Rune Oct 27 '12 at 20:01
  • Hmmm. Could you check your windows log for Event ID 4228? http://community.spiceworks.com/windows_event/show/3080-tcpip-4228 –  Oct 27 '12 at 20:05
  • What I was orginally referring to, was a limitation to prevent people from using a client OS on a file server. Haven't seen this confirmed yet, but googling around I found the link I just posted. So it could be something different altogether. –  Oct 27 '12 at 20:08
  • There might just be something to `ServicePointManager` after all, have a look at this question and it might help you a bit on the way. http://stackoverflow.com/questions/866350/how-can-i-programmatically-remove-the-2-connection-limit-in-webclient – Karl-Johan Sjögren Oct 27 '12 at 20:08
  • Thanks for your input guys. I've checked the suggestions: no events with ID 4228, changing ServicePoint.ConnectionLimit has no effect. – Rune Oct 27 '12 at 20:21
  • 1
    @Rune TPL also tries to optimize the number of threads you use. Just try to use pure `Thread` class. – L.B Oct 27 '12 at 20:22
  • Agree with @L.B - this probably is a thread pool issue, and not related to WebClient - use threads directly to rule this out – BrokenGlass Oct 27 '12 at 20:26
  • Using just plain new Thread(...).Start() shows the same behaviour. – Rune Oct 27 '12 at 20:35
  • @Rune assuming you tried it with setting `ServicePointmanager.DefaultConnectionLimit` to a reasonable number. – L.B Oct 27 '12 at 20:38
  • Yeah, I thought 20 seemed reasonable? – Rune Oct 27 '12 at 20:51
  • On Windows 2008 Server R2 Enterprise I don't see the cap at 10 concurrent requests, so I guess it must be OS specific. Sigh. – Rune Oct 27 '12 at 21:09
  • Might be 10 inbound requests - from Win7 point it at the 2008 R2 server in the URL – James Manning Oct 27 '12 at 21:15
  • @JamesManning Good point. I'll check when I get into the office tomorrow (firewall issues prevent me from checking at the moment). Thanks. – Rune Oct 27 '12 at 21:20
  • Async IO has nothing to do with the thread-pool (except that completion callbacks are queued there). See my answer. – usr Oct 27 '12 at 23:06

1 Answers1

6

You client is able to send arbitrarily many concurrent requests. Your server, though, is localhost. Client Windows OS's have a limitation on the number of concurrent requests in web applications (so that you have to buy a server license). This limit is 10. You can't do anything about it.

Btw, your app is using async IO so you don't need any threads (not even implicit thread-pool threads) while the IO is running. This is not your problem.

usr
  • 168,620
  • 35
  • 240
  • 369
  • That I can have more outstanding requests than threads in the pool was exactly what I was trying to demonstrate when I happened upon the behaviour described above. Anyway, it all makes sense now. When I run it against WS2008 the ServicePoint.ConnectionLimit kicks in. When I raise that, I can get as many outstanding requests as I like. Thank you, much appreciated. – Rune Oct 28 '12 at 09:15