C# Threading Trouble

Posted by John Kleijn • Saturday, January 19. 2008 • Category: Development

I decided to write a small C# application to facilitate stress testing of Web applications. The reason I didn't want to use csharp (with sockets or the cURL extension) is because I wanted to use threading. I could've gone with Java, but I fell for the ease of creating a GUI with Visual Studio.

I don't have much experience with C#, but with a little help from Google and MSDN, I was quickly able to create a simple application that makes requests and measures the time it takes to receive the response.


It worked fine, but it didn't really feel like I was pushing any limits. In came the threading part. I guess that, by some standards, threading in C# can be considered easy. Sure enough, after struggling a bit with synchronisation, there was my multithreaded test program. The below method manages the thread pool.


public long run(int maxThreads, int repeats, int timeout)
{
        TestResult aTestResult = new TestResult(repeats);
        Tester aTester = new Tester(timeout, aTestResult, this.ev);
        ThreadPool.SetMaxThreads(maxThreads, maxThreads);

        for (int i = 0; i < repeats; ++i)
        {
                ThreadPool.QueueUserWorkItem(new WaitCallback(aTester.testCallback));
        }

        this.ev.WaitOne(30000, true);

        try
        {
                long avg = aTester.getResult().getAvg();
                return avg;
        }
        catch (DivideByZeroException)
        {
                return (long) 0;
        }
}
 

In short, it creates threads and lets them execute 'aTester.testCallBack'. The threads are queued and reused until we run out of 'repeats', which equals the total number of tests we want to run.

This line:

this.ev.WaitOne(30000, true);

indicates that the main thread is halted until either 30 seconds pass, or the event referenced by 'ev' is fired. The 'this' keyword is not required, but I prefer a clear distinction between local variables and object properties. This event is passed to the Tester class, and threads are allowed to fire it off. Here's the callback originally used, to give you an idea:


public void testCallback(Object o)
{
        long singleResult = this.doTest();

        lock (Tester.result)
        {
                Tester.result.add(singleResult);

                if (Tester.result.isFull())
                {
                        this.fireEvent();
                }
        }
}
 

'Tester.fireEvent' just invokes the 'Set' method on the event. TestResult instance 'result' is made static, by recommendation of MSDN. You'll notice the 'lock' keyword, which basically means only one thread can enter that block of code at the time. This is to prevent threads writing test results to the same array index.

Next, the actual test. This is where the problems arise.


public long doTest()
{
        HttpWebRequest req = (HttpWebRequest)WebRequest.CreateDefault(Bench.uri);
        req.Method = "GET";

        req.ReadWriteTimeout = 1000;

        Stopwatch sw = new Stopwatch();

        sw.Start();
        HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

        sw.Stop();
        resp.Close();

        return sw.ElapsedMilliseconds;
}
 

As you can see, it's a very simple test script. It just makes a request and times how long it takes to receive the response. The problem is that the instance of StopWatch is not shielded from concurrent threads. The results are therefore useless (and typically higher than with the single threaded version). It is not possible to 'lock' only the operations on 'sw', without locking 'req.GetResponse()', which is not what we want. Doing so would more or less be equivalent to this:


public void testCallback(Object o)
{
        lock (Tester.result)
        {
                long singleResult = this.doTest();

                Tester.result.add(singleResult);

                if (Tester.result.isFull())
                {
                        this.fireEvent();
                }
        }
}
 

You see the problem here? We create multiple threads, and then let them all wait to do ANY processing at all.

I've tried al sorts of things, including creating a container which maps a thread code to a start time in a static hash table, to no avail.

To be continued. Maybe. ;-)

2009-03-24: Obviously this was never continued and I really haven't done any C# since

0 Trackbacks

  1. No Trackbacks

0 Comments

Display comments as (Linear | Threaded)
  1. No comments

Add Comment

You can use [geshi lang=lang_name [,ln={y|n}]][/geshi] tags to embed source code snippets.
Standard emoticons like :-) and ;-) are converted to images.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA


Antiquities and such