views:

227

answers:

3

I have an ASP.NET page with this pseduo code:

while (read)
{
   Response.OutputStream.Write(buffer, 0, buffer.Length);
   Response.Flush();
}

Any client who requests this page will start to download a binary file. Everything is OK at this point but clients had no limit in download speed so changed the above code to this:

while (read)
{
   Response.OutputStream.Write(buffer, 0, buffer.Length);
   Response.Flush();
   Thread.Sleep(500);
}

Speed problem is solved now, but under test with 100 concurrent clients who connect one after another (3 seconds lag between each new connection) the CPU usage increases when the number of clients increases and when there are 70 ~ 80 concurrent clients CPU reaches 100% and any new connection is refused. Numbers may be different on other machines but the question is why Thread.Sleep() is so CPU intensive and is there any way to speed done the client without CPU rising ?

I can do it at IIS level but I need more control from inside of my application.

+14  A: 

Just a guess:

I don't think it's Thread.Sleep() that's tying up the CPU - it's the fact that you're causing threads to be tied up responding to a request for so long, and the system needs to spin up new threads (and other resources) to respond to new requests since those sleeping threads are no longer available in the thread pool.

Michael Burr
+1 Spinning up new threads is /very/ expensive, and it's very easy to get thread starvation on IIS
FacticiusVir
I repeated the test with 100 client with no download speed limit. I'm sure the only difference between the code that don't increase the CPU and the one that do so is just Thread.Sleep(). I have tests for longer times with more clients and the CPU was at 1~2 % all the time.
Xaqron
@ FacticiusVir: I don't spin. I use 'Sleep'. So the ASP.NET worker thread releases the CPU. Using IIS settings it's possible to set a connection speed limit which is not my choice here.
Xaqron
But you still saturate the work pool. You should not use sleep but async IO API.
TomTom
@TomTom: Would you please give an example link or a hint related to my case (Sending binary data to clients from ASP.NET app)
Xaqron
+1  A: 

Rather than an ASP.NET page you should implement an IHttpAsyncHandler. ASP.NET page code puts many things between your code and the browser that would not be appropriate for transferring binary files. Also, since you're attempting to perform rate limitation, you should use asynchronous code to limit resource usage, which would be difficult in an ASP.NET page. Creating an IHttpAsyncHandler is fairly simple. Just trigger some asynchronous operations in the BeginProcessRequest method, and don't forget to properly close the context to show you have reached the end of the file. IIS won't be able to close it for you here.

The following is my really bad example of how to perform an an asynchronous operation consisting of a series of steps, counting from 0 to 10, each performed at a 500ms interval.

using System;
using System.Threading;

namespace ConsoleApplication1 {
    class Program {
        static void Main() {
            // Create IO instances
            EventWaitHandle WaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset); // We don't actually fire this event, just need a ref
            EventWaitHandle StopWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
            int Counter = 0;
            WaitOrTimerCallback AsyncIOMethod = (s, t) => { };
            AsyncIOMethod = (s, t) => {
                // Handle IO step
                Counter++;
                Console.WriteLine(Counter);
                if (Counter >= 10)
                    // Counter has reaced 10 so we stop
                    StopWaitHandle.Set();
                else
                    // Register the next step in the thread pool
                    ThreadPool.RegisterWaitForSingleObject(WaitHandle, AsyncIOMethod, null, 500, true);
            };

            // Do initial IO
            Console.WriteLine(Counter);
            // Register the first step in the thread pool
            ThreadPool.RegisterWaitForSingleObject(WaitHandle, AsyncIOMethod, null, 500, true);
            // We force the main thread to wait here so that the demo doesn't close instantly
            StopWaitHandle.WaitOne();
        }
    }
}

You'll also need to register your IHttpAsyncHandler implementation with IIS in whichever way is appropriate for your situation.

Lunatic Experimentalist
I deal with many other things like cookies, redirecting users to other pages (HTTP redirect)... and I'm not sure if I with this solution I need to go deep inside IIS and handle everything myself. I would read about it and vote. Thanks for the hint.
Xaqron
+4  A: 

Let's take a look at whether Michael's answer seems reasonable.

Now, Michael wisely points out that Thread.Sleep(500) shouldn't cost much in the way of CPU. That's all well and good in theory, but let's see if that pans out in practice.

    static void Main(string[] args) {
        for(int i = 0; i != 10000; ++i)
        {
            Thread.Sleep(500);
        }
    }

Running this, the CPU use of the application hovers around the 0% mark.

Michael also points out that since all the threads that ASP.NET has to use are sleeping, it will have to spawn new threads, and offers that this is expensive. Let's try not sleeping, but doing lots of spawning:

    static void Main(string[] args) {
        for(int i = 0; i != 10000; ++i)
        {
            new Thread(o => {}).Start();
        }
    }

We create lots of threads, but they just execute a null operation. That uses a lot of CPU, even though the threads aren't doing anything.

The total number of threads never gets very high though, because each lives for such a short time. Lets combine the two:

    static void Main(string[] args) {
        for(int i = 0; i != 10000; ++i)
        {
            new Thread(o => {Thread.Sleep(500);}).Start();
        }
    }

Adding this operation that we have shown to be low in CPU use to each thread increases CPU use even more, as the threads mount up. If I run it in a debugger it pushes up to near 100% CPU. If I run it outside of a debugger, it performs a bit better, but only because it throws an out of memory exception before it gets a chance to hit 100%.

So, it isn't Thread.Sleep itself that is the problem, but the side-effect that having all available threads sleep forces more and more threads to be created to handle other work, just as Michael said.

Jon Hanna
Thanks Jon. So so say when my threads are working they are less than (in number) when they are working (this makes sense) ? As I told I repeated the test with the same condition without Thread.Sleep() and everything was fine with 100 clients. I meant all 100 clients were connected and receiving data. But same threads begin to increase CPU usage when I added Thread.Sleep() and nothing more. What do you think about IHttpAsyncHandler (thanks Lunatic) and redesigning the work ? (I was wondering if ASP.NET is good for that kind of work)
Xaqron
Because of the way tasks are dealt with by a pool, 1 physical thread may be serving more than 1 client. However, lock up a thread by sleeping it, and it no longer can, so the number of threads will increase even though number of clients doesn't. An async handler is a good approach to long-delayed asp tasks generally, but I'm not sure what you are actually doing the sleep for. If you want to throttle data, maybe use IIS7 bit-rate throttling; it can be used with non-media files with no bit-rates according to http://learn.iis.net/page.aspx/149/bit-rate-throttling-extensibility-walkthrough/
Jon Hanna