Threads and Collections in C#


Introduction

The Collection classes in the System.Collections namespace are not threadsafe and their behavior is "undefined" when collisions occur. This program illustrates the issue:

using System;
using System.Collections;
using System.Threading;
class SyncCol1
{
    public static void Main()
    {
        int iThreads = 25; 
        SyncCol1 sc1 = new SyncCol1(iThreads);
        Console.Read();
    }
    int iThreads;
    SortedList myList;
    public SyncCol1(int iThreads)
    {
        this.iThreads = iThreads;
        myList = new SortedList();
        TimedWrite(myList);
        myList = SortedList.Synchronized(new
SortedList());
        TimedWrite(myList);
    }
       public void TimedWrite(SortedList myList)
       {
        WriterThread.ExceptionCount = 0;
        WriterThread[] writerThreads = new WriterThread[iThreads];
        DateTime start = DateTime.Now;
        for(int i = 0; i < iThreads; i++)
        {
            writerThreads[i] = new WriterThread(myList, i);
            writerThreads[i].Start();
        }
        WaitForAllThreads(writerThreads);
        DateTime stop = DateTime.Now;
        TimeSpan elapsed = stop - start;
        Console.WriteLine("Synchronized List: " + myList.IsSynchronized);
        Console.WriteLine(iThreads + " * 5000 = " + myList.Count + "? " + (myList.Count == (iThreads * 5000)));
        Console.WriteLine("Number of exceptions thrown: " + WriterThread.ExceptionCount);
        Console.WriteLine("Time of calculation = " + elapsed);
    }
    public void WaitForAllThreads(WriterThread[] ts)
    {
        for(int i = 0; i < ts.Length; i++)
        {
            while(ts[i].Finished == false)
            {
                Thread.Sleep(1000);
            }
        }
    }
}
class WriterThread
   {
    static int iExceptionsThrown = 0;
    public static int ExceptionCount
    {
        get{ return iExceptionsThrown; }
        set{ iExceptionsThrown = value; }
    }
    Thread t;
    SortedList theList;
    public WriterThread(SortedList theList, int i)
    {
        t = new Thread(new ThreadStart(WriteThread));
        t.IsBackground = true;
        t.Name = "Writer[" + i.ToString() + "]";
        this.theList = theList;
    }
    public bool Finished
    {
        get{ return isFinished; }
    }
    bool isFinished = false;
    public void WriteThread()
    {
        for(int loop = 0; loop < 5000; loop++)
        {
            String elName = t.Name + loop.ToString();
            try
            {
                theList.Add(elName, elName);
            }
            catch(Exception )
            {             
               ++iExceptionsThrown;
            }
        }
        isFinished = true;
    }
    public void Start()
    {
        t.Start();
    }
}

Output

threading and collection.jpg

Main() generates a SyncCol1 class whose parameter indicating how  many threads to simultaneously write to a collection. A  SortedList is created and ed to the TimedWrite() method. This method sets the static variable ExceptionCount of the WriterThread class to 0 and creates an array of WriterThread.. The WriterThread constructor takes the list and a variable. Each WriterThread creates a new thread, whose processing is delegated to the WriterThread.WriteThread() method. The Background property of the WriterThread's thread is set to true. A program won't exit until all it's non-background threads have ended.

After the WriterThread constructor returns, the next line of TimedWrite() calls the Start() method, which in turn starts the inner thread, which in turn delegates processing to WriteThread(). WriteThread() loops 5,000 times, each time creating a new name (such as "Writer[12]237") and attempting to add that to theList . A SortedArray is backed by two stores – one to store the values and another to store a sorted list of keys (the keys may or may not be the same as the values).

Back to WriterThread.WriteThread(). The call to Add() an element to the list is wrapped in a catch block. Since we are ignoring the details of the exception and only recording how many exceptions were thrown, the catch statement does not specify a variable name for the caught exception. Once the loop is finished, we set the Finished property of the WriterThread, kill the Thread, and return. Back in the SyncCol1 class, the main application thread goes through the array, checking to see if it's finished. If it's not, the main thread goes to sleep for 1,000 seconds before
checking again. When all the WriterThread's are Finished, the WriteThread() writes some data on the experiment and returns.

After the initial call with a regular SortedList, we create a new SortedList and it to the static method SortedList.Synchronized() . All the Collections have this static method, which creates a new, thread-s afe Collection. To be clear, the program creates a total of 3 SortedList: the one for the initial run through
WriteThread(), a second anonymous one, which is used as the parameter to
SortedList.Synchronize(), which returns a third one.

When you run this program, you'll see that the first run, with a plain SortedList throws a large number of exceptions (if you have a sufficiently speedy computer, you may get no exceptions, but if you increase the number of threads, eventually you'll run into trouble), while the list produced by Synchronized() adds all the data flawlessly.  You'll
also see why Collections aren't synchronized by default.

Up Next
    Ebook Download
    View all
    Learn
    View all