У меня есть сложная проблема, которая возникает в некоторых частях моего кода. У меня есть диспетчер кеша, который либо возвращает элементы из кеша, либо вызывает делегата для их создания (дорого).
Я обнаружил, что у меня проблемы с финальной частью моего метода, которая запускается в другом потоке, чем остальные.
Вот урезанная версия
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 заполняет словарь объектами LockPoolItem, которые были заблокированы с помощью Monitor.Enter/Monitor.Exit [я также пробовал мьютексы]. Проблема возникает, когда ReleaseLock вызывается в потоке, отличном от того, в котором был вызван AcquireLock.
Проблема возникает при вызове этого из другой функции, которая использует потоки, иногда вызывается блок finalize из-за удаления IEnumerator, работающего на возвращаемой итерации.
Следующий блок является простым примером.
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();
});
}
Кажется, этого не происходит, когда я добавляю разрыв выхода, однако мне трудно отлаживать это, поскольку это происходит очень редко. Тем не менее, это происходит - я пытался регистрировать идентификаторы потоков и завершать работу, если меня вызывают в разных потоках...
Я уверен, что это не может быть правильным поведением: я не понимаю, почему метод dispose (т.е. выход из использования) вызывается в другом потоке.
Любые идеи, как защититься от этого?
MoveNext
или, действительно, как вы обнаружили,Dispose
. Не зная больше о вашей конкретной проблеме, трудно дать конкретный совет, но именно на это я бы обратил внимание - измените дизайн, чтобы вы не зависели от милости вызывающих абонентов, когда вы снимаете блокировки. - person Damien_The_Unbeliever   schedule 24.04.2015async
) - person Damien_The_Unbeliever   schedule 27.04.2015