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
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.