This sounds much like a lock manager, so why not build one?
class LockManager<TKey>
{
private Dictionary<TKey, List<EventWaitHandle>> locks =
new Dictionary<TKey, List<EventWaitHandle>>();
private Object syncRoot = new Object();
public void Lock(TKey key)
{
do
{
Monitor.Enter(syncRoot);
List<EventWaitHandle> waiters = null;
if (true == locks.TryGetValue(key, out waiters))
{
// Key is locked, add ourself to waiting list
// Not that this code is not safe under OOM conditions
AutoResetEvent eventLockFree = new AutoResetEvent(false);
waiters.Add(eventLockFree);
Monitor.Exit(syncRoot);
// Now wait for a notification
eventLockFree.WaitOne();
}
else
{
// event is free
waiters = new List<EventWaitHandle>();
locks.Add(key, waiters);
Monitor.Exit(syncRoot);
// we're done
break;
}
} while (true);
}
public void Release(TKey key)
{
List<EventWaitHandle> waiters = null;
lock (syncRoot)
{
if (false == locks.TryGetValue(key, out waiters))
{
Debug.Assert(false, "Releasing a bad lock!");
}
locks.Remove(key);
}
// Notify ALL waiters. Unfair notifications
// are better than FIFO for reasons of lock convoys
foreach (EventWaitHandle waitHandle in waiters)
{
waitHandle.Set();
}
}
}
You must lock each value before you use it:
class Program
{
class ThreadData
{
public LockManager<int> LockManager { get; set; }
public int[] Work { get; set; }
public AutoResetEvent Done { get; set; }
}
static void Main(string[] args)
{
int[] forA = new int[] {5, 8, 9, 12};
int[] forB = new int[] {7, 8, 9, 13, 14 };
LockManager<int> lockManager = new LockManager<int>();
ThreadData tdA = new ThreadData
{
LockManager = lockManager,
Work = forA,
Done = new AutoResetEvent(false),
};
ThreadData tdB = new ThreadData
{
LockManager = lockManager,
Work = forB,
Done = new AutoResetEvent(false),
};
ThreadPool.QueueUserWorkItem(new WaitCallback(Worker), tdA);
ThreadPool.QueueUserWorkItem(new WaitCallback(Worker), tdB);
WaitHandle.WaitAll(new WaitHandle[] { tdA.Done, tdB.Done });
}
static void Worker(object args)
{
Debug.Assert(args is ThreadData);
ThreadData data = (ThreadData) args;
try
{
foreach (int key in data.Work)
{
data.LockManager.Lock(key);
Console.WriteLine("~{0}: {1}",
Thread.CurrentThread.ManagedThreadId, key);
// simulate the load the set for Key
Thread.Sleep(1000);
}
foreach (int key in data.Work)
{
// Now free they locked keys
data.LockManager.Release(key);
}
}
catch (Exception e)
{
Debug.Write(e);
}
finally
{
data.Done.Set();
}
}
}
The biggest problem you'll face will be deadlocks. Change the two arrays to {5,8,9,7} and {7,8,9,5} and you'll see my point immedteatly.