I have a tricky problem that is turning up in some of my code. I have a cache manager that either returns items from the cache or calls a delegate to create them (expensively).
I'm finding that I'm having problems with the finalize part of my method being run on a different thread than the rest.
Here's a cut down version
public IEnumerable<Tuple<string, T>> CacheGetBatchT<T>(IEnumerable<string> ids, BatchFuncT<T> factory_fn) where T : class
{
Dictionary<string, LockPoolItem> missing = new Dictionary<string, LockPoolItem>();
try
{
foreach (string id in ids.Distinct())
{
LockPoolItem lk = AcquireLock(id);
T item;
item = (T)resCache.GetData(id); // try and get from cache
if (item != null)
{
ReleaseLock(lk);
yield return new Tuple<string, T>(id, item);
}
else
missing.Add(id, lk);
}
foreach (Tuple<string, T> i in factory_fn(missing.Keys.ToList()))
{
resCache.Add(i.Item1, i.Item2);
yield return i;
}
yield break; // why is this needed?
}
finally
{
foreach (string s in missing.Keys)
{
ReleaseLock(l);
}
}
}
Acquire and Release lock fill a dictionary with LockPoolItem objects that have been locked with Monitor.Enter / Monitor.Exit [I have also tried mutexes]. The problem is coming when ReleaseLock is being called on a different thread from the one AcquireLock was called on.
The problem comes when calling this from another function that uses threads sometimes the finalize block gets called, due to the disposal of the IEnumerator running on the returned iterate.
The following block is a simple example.
BlockingCollection<Tuple<Guid, int>> c = new BlockingCollection<Tuple<Guid,int>>();
using (IEnumerator<Tuple<Guid, int>> iter = global.NarrowItemResultRepository.Narrow_GetCount_Batch(userData.NarrowItems, dicId2Nar.Values).GetEnumerator()) {
Task.Factory.StartNew(() => {
while (iter.MoveNext()) {
c.Add(iter.Current);
}
c.CompleteAdding();
});
}
This doesn't seem to happen when I add the yield break - however I'm finding this hard to debug as it only occurs very occasionally. However, it does happen - I've tried logging the thread ids and finalize if getting called on different threads...
I'm sure this can't be correct behaviour: I can't see why the dispose method (i.e. exit using) would get called on a different thread.
Any ideas how to guard against this?