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<double> with {2} reader threads.", listTime.TotalMilliseconds, collectionSize, readerThreadCount);
Console.WriteLine("Took {0} ms to add {1} elements to a ConcurrentBag<double> 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<T, TCollection>
{
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<TCollection>();
}
protected abstract void AddToCollection(TCollection collection, T item);
protected abstract void EnumerateCollection(TCollection collection);
protected abstract T CreateElement(int index);
}
sealed class LockedListTester : ConcurrentCollectionTester<double, List<double>>
{
// For writes/reads to/from a List<T> to be safe, we'll need to lock
// both.
private object m_lock = new object();
protected override void AddToCollection(List<double> collection, double item)
{
lock (m_lock)
{
collection.Add(item);
}
}
protected override void EnumerateCollection(List<double> collection)
{
lock (m_lock)
{
double sum = collection.Sum();
}
}
protected override double CreateElement(int index)
{
return (double)index;
}
}
sealed class ConcurrentBagTester : ConcurrentCollectionTester<double, ConcurrentBag<double>>
{
// A ConcurrentBag<T> is supposed to be thread-safe with no need for
// any locking.
protected override void AddToCollection(ConcurrentBag<double> collection, double item)
{
collection.Add(item);
}
protected override void EnumerateCollection(ConcurrentBag<double> collection)
{
double sum = collection.Sum();
}
protected override double CreateElement(int index)
{
return (double)index;
}
}
}