using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace ConcurrentBagTest { class Program { static void Main(string[] args) { var listTester = new LockedListTester(); var bagTester = new ConcurrentBagTester(); do { try { int collectionSize = GetNumber("Collection size"); int readerThreadCount = GetNumber("Number of reader threads to create"); int trialCount = GetNumber("Number of trials to run"); TimeSpan totalListTime = TimeSpan.Zero, totalBagTime = TimeSpan.Zero; for (int i = 0; i < trialCount; ++i) { TimeSpan listTime = listTester.TestSimultaneousReadsWrites(collectionSize, readerThreadCount); TimeSpan bagTime = bagTester.TestSimultaneousReadsWrites(collectionSize, readerThreadCount); totalListTime += listTime; totalBagTime += bagTime; Console.WriteLine("Took {0} ms to add {1} elements to a List with {2} reader threads.", listTime.TotalMilliseconds, collectionSize, readerThreadCount); Console.WriteLine("Took {0} ms to add {1} elements to a ConcurrentBag with {2} reader threads.", bagTime.TotalMilliseconds, collectionSize, readerThreadCount); } Console.WriteLine("Average list time: {0} ms.", totalListTime.TotalMilliseconds / trialCount); Console.WriteLine("Average bag time: {0} ms.", totalBagTime.TotalMilliseconds / trialCount); } finally { Console.Write("Go again? "); } } while (Console.ReadLine().StartsWith("y", StringComparison.OrdinalIgnoreCase)); } static int GetNumber(string prompt) { Console.Write("{0}: ", prompt.TrimEnd(':', ' ')); int input; while (!int.TryParse(Console.ReadLine(), out input)) { Console.Write("That was not a valid number--try again: "); } return input; } } abstract class ConcurrentCollectionTester { public TimeSpan TestSimultaneousReadsWrites(int collectionSize, int readerThreadCount) { // Keep the playing field even. GC.Collect(); TCollection collection = CreateCollection(collectionSize); var threads = new Thread[readerThreadCount]; using (var stopReaders = new ManualResetEvent(false)) { int readersFinished = 0; // Spin off a bunch of reader threads continuously enumerating // over the collection. for (int i = 0; i < threads.Length; ++i) { threads[i] = new Thread(() => { while (!stopReaders.WaitOne(0)) { EnumerateCollection(collection); } Interlocked.Increment(ref readersFinished); } ); threads[i].Start(); } // Now see how long it takes to populate the collection while // all those readers are going. Stopwatch stopwatch = Stopwatch.StartNew(); Parallel.For(0, collectionSize, i => { T element = CreateElement(i); AddToCollection(collection, element); } ); stopwatch.Stop(); // Signal that the readers can stop. stopReaders.Set(); // Wait for the last reader to finish (not really relevant to // this test--just needed to avoid a race condition that would // throw an ObjectDisposedException on the WaitHandle). SpinWait.SpinUntil(() => readersFinished == readerThreadCount); return stopwatch.Elapsed; } } protected virtual TCollection CreateCollection(int collectionSize) { return Activator.CreateInstance(); } protected abstract void AddToCollection(TCollection collection, T item); protected abstract void EnumerateCollection(TCollection collection); protected abstract T CreateElement(int index); } sealed class LockedListTester : ConcurrentCollectionTester> { // For writes/reads to/from a List to be safe, we'll need to lock // both. private object m_lock = new object(); protected override void AddToCollection(List collection, double item) { lock (m_lock) { collection.Add(item); } } protected override void EnumerateCollection(List collection) { lock (m_lock) { double sum = collection.Sum(); } } protected override double CreateElement(int index) { return (double)index; } } sealed class ConcurrentBagTester : ConcurrentCollectionTester> { // A ConcurrentBag is supposed to be thread-safe with no need for // any locking. protected override void AddToCollection(ConcurrentBag collection, double item) { collection.Add(item); } protected override void EnumerateCollection(ConcurrentBag collection) { double sum = collection.Sum(); } protected override double CreateElement(int index) { return (double)index; } } }