C#: Non-ideal MSDN Threading Tutorial Examples

I’m partway through the MSDN C# Threading Tutorial and, as a Java developer, have had fun breaking/refactoring their tutorial code. I haven’t yet gotten to the Mutex section, mostly because I spent some time to learn about System.Collections.Concurrent.

Using MonoDevelop, I’ve been modifying the examples and playing. Whoever wrote the tutorial is inconsistent with capitalization of field names, comes up with terrible names (seriously, when “Example” and “Call” are more descriptive than your chosen names of “Alpha” and “Beta”, you have an issue), and hasn’t proofed their tutorials against malicious students.

Primary instance of non-proofing: the Monitor example. You can introduce failure (inconsistently) via deadlock by simply adding either a second producer or a second consumer thread. You need something like Java’s CountDownLatch, which apparently exists as System.Threading.CountdownEvent… but I haven’t tried it out yet.

What I did find interesting was sorting out the mess that was the simple ThreadPool example. I used a concurrent dictionary (thank you generics), cleaned up variable names where I could (seriously, who decided that only W2K supported thread pooling? Even if you assume Windows-only, I can’t imagine that Vista or Windows 7 come without it. In other words: things change. Try to be timeless with your variable names).

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Threading;

namespace SimpleThreadPool
{
    /// <summary>
    /// Simple value holder. Also demonstrates that C# has 
    /// a complicated outlook on keywords.
    /// </summary>
    public class Cookie
    {
        public int value;
        public Cookie(int value)
        {
            this.value = value;
        }
    }
    
    /// <summary>
    /// Callback class that keeps track of which threads hashed to which hashcodes.
    /// </summary>
    public class Example
    {
        public ConcurrentDictionary<int, int> HashCount;
        public ManualResetEvent resetEvent;
        public static int count = 0;
        public static int maxCount = 0;
        
        public Example(int maxCount)
        {
            HashCount = new ConcurrentDictionary<int, int>(10, 10);
            Example.maxCount = maxCount;
        }
        
        public void Call(Object o)
        {
            int currentThreadHashCode = Thread.CurrentThread.GetHashCode();
            
            Console.WriteLine ("#{0} {1} :", 
                               currentThreadHashCode, ((Cookie) o).value);
            Console.WriteLine("HashCount.Count={0}, CurrentThread.GetHashCode()={1}",
                               HashCount.Count, currentThreadHashCode);
            
            // Lambda expressions are radical.
            HashCount.AddOrUpdate (currentThreadHashCode, 1, ((x,y) => y+1));
            
            Thread.Sleep (2000);
            Interlocked.Increment(ref count);
            if (count == maxCount) {
                Console.WriteLine("Setting reset event.");
                resetEvent.Set();
            }
        }
    }
    
    class MainClass
    {
        public static void Main (string[] args)
        {
            const int maxCount = 10;
            
            ManualResetEvent resetEvent = new ManualResetEvent(false);
            
            Example example = new Example(maxCount);
            example.resetEvent = resetEvent;
            
            bool threadPoolSupported = false;
            
            try {
                ThreadPool.QueueUserWorkItem(new WaitCallback(example.Call), 
                                             new Cookie(0));
                threadPoolSupported = true;
            }
            catch (NotSupportedException e)
            {
                Console.WriteLine("ThreadPool is unsupported: #{0}", e);
            }
            
            if (threadPoolSupported)
            {
                for (int i = 1; i < maxCount; i++) {
                    ThreadPool.QueueUserWorkItem(new WaitCallback(example.Call), 
                                                 new Cookie(i));
                }
                
                Console.WriteLine ("Waiting for thread pool to drain.");
                resetEvent.WaitOne(Timeout.Infinite, true);
                Console.WriteLine ("Thread pool has been drained (event fired).");
                
                Console.WriteLine ();
                Console.WriteLine ("Load across threads:");
                foreach(int o in example.HashCount.Keys) {
                    Console.WriteLine("#{0} {1}", o, example.HashCount[o]);
                }
            }
        }
    }
}

I truly wish that the libraries were better documented. Java docs tend to come with examples; C# docs generally don’t.

And I know this is “simply” tutorial code, but that’s the point. It’s teaching code. You can teach bad coding habits into people.

And now I need to review stuff for OWW. It’s a nice day, but I’m stuck inside due to oncall unless I drag my laptop and associated peripherals around with me. Still, I may go out for an early dinner.

Advertisements