У меня есть сложная проблема, возникающая в некоторых моих кодах. У меня есть менеджер кэша, который либо возвращает элементы из кеша, либо вызывает делегат для их создания (дорого).В функции возврата доходности можно гарантировать, что финализатор вызывается в том же потоке?
Я обнаружил, что у меня проблемы с завершающей частью моего метода, выполняемой в другом потоке, чем остальные.
Вот урезанная версия
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);
}
}
}
Приобретать и снятия блокировки заполнить словарь с объектами 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». Не зная больше о вашей конкретной проблеме, сложно предложить конкретные советы, но я буду искать - измените дизайн, чтобы вы не пощадили своих абонентов, когда вы отпускаете замки. –
Это справедливая точка, однако она не отвечает на вопрос. То, что я пытаюсь достичь, состоит в том, чтобы вернуть поставщику элементы, поскольку они извлекаются из медленного хранилища - некоторые элементы могут занимать секунды, другие миллисекунды, но нет способа узнать, перед какими из них в партии будет медленно возвращаться. Я подозреваю, что мне будет лучше иметь блокирующую коллекцию, предоставляемую функции кеша, и заполнить ее там. Однако я до сих пор не понимаю, почему dispose/finalize вызывается в другом потоке. – dominicbeesley
Нет, это не так, поэтому почему оно опубликовано как комментарий. Если вы готовы внести такие изменения, я могу приложить некоторые усилия и показать вам, как может выглядеть альтернатива.Если вы проверите обсуждение, которое у меня было после ответа supercat, вы увидите, что я несколько дней спорил о том, что нет гарантии, что перечислимое будет возобновлено в одном потоке (особенно если код вызова использует современные функции, такие как ' async') –