У меня есть служба WCF, где InstanceContextMode
- Single
и ConcurrencyMode
- Multiple
. Целью является создание кеша значений при создании экземпляра, не поддерживая другие вызовы службы, не зависящие от создания кеша.ReaderWriterLock не работает в ServiceBehavior constructor
Таким образом, только методы, которые пытаются получить блокировку чтения на _classificationsCacheLock
, должны будут ждать, пока не будет заполнено значение classificationsCache
(classificationsCacheLock.IsWriterLockHeld = false
).
Однако вопрос, несмотря на приобретение блокировки записи в задаче потока, WCF вызов продолжает службу в ответ на вызов в сервисный метод GetFOIRequestClassificationsList()
приводит к _classificationsCacheLock.IsWriterLockHeld
будучи false
, когда это должно быть правдой.
Это странное поведение с WCF
instancing или это я принципиально не упускаю трюк.
Я попробовал и получение блокировки записи в контексте резьбы конструктора (безопасный вариант) и в контексте порождал задачи потока (который может ввести гонки между WCF
последующим вызовом на вызов функции GetFOIRequestClassificationsList()
быстрее, чем вызов до classificationsCacheLock.AcquireWriterLock(Timeout.Infinite);
), но оба результата приводят к classificationsCacheLock.IsWriterLockHeld
, являющимся false
, несмотря на то, что они предотвратили любое состояние гонки с помощью thread.sleep, соответствующим образом разнесенные в блоках кода каждой соответствующей нити.
[ServiceBehavior(Namespace = Namespaces.MyNamespace,
ConcurrencyMode = ConcurrencyMode.Multiple,
InstanceContextMode = InstanceContextMode.Single)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MyService : IMyService
{
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
private List<string> _classificationsCache;
private ReaderWriterLock _classificationsCacheLock;
public MyService()
{
try
{
_classificationsCacheLock = new ReaderWriterLock();
LoadCache();
}
catch (Exception ex)
{
_logger.Error(ex);
}
}
private void LoadCache()
{
// _classificationsCacheLock.AcquireWriterLock(Timeout.Infinite);
Task.Factory.StartNew(() =>
{
try
{
_classificationsCacheLock.AcquireWriterLock(Timeout.Infinite); // can only set writer on or off on same thread, not between threads
if (_classificationsCache == null)
{
var cases = SomeServices.GetAllFOIRequests();
_classificationsCache = cases.SelectMany(c => c.Classifications.Classification.Select(cl => cl.Group)).Distinct().ToList();
}
}
catch (Exception ex)
{
_logger.Error(ex);
}
finally
{
if (_classificationsCacheLock.IsWriterLockHeld)
_classificationsCacheLock.ReleaseWriterLock();
}
});//.ContinueWith((prevTask) =>
//{
// if (_classificationsCacheLock.IsWriterLockHeld)
// _classificationsCacheLock.ReleaseWriterLock();
// });
}
public GetFOIRequestClassificationsList_Response GetFOIRequestClassificationsList()
{
try
{
GetFOIRequestClassificationsList_Response response = new GetFOIRequestClassificationsList_Response();
_classificationsCacheLock.AcquireReaderLock(Timeout.Infinite);
response.Classifications = _classificationsCache;
_classificationsCacheLock.ReleaseReaderLock();
return response;
}
catch (Exception ex)
{
_logger.Error(ex);
if (ex is FaultException)
{
throw;
}
else
throw new FaultException(ex.Message);
}
}
}
EDIT 1
Поскольку ряд предложений были вокруг неопределенности в пуле потоков и как Task может обрабатывать сродство нити, я изменил метод явно нереста нового потока
var newThread = new Thread(new ThreadStart(() =>
{
try
{
Thread.Sleep(2000);
Debug.WriteLine(string.Format("LoadCache - _classificationsCacheLock.GetHashCode - {0}", _classificationsCacheLock.GetHashCode()));
Debug.WriteLine(string.Format("LoadCache - Thread.CurrentThread.ManagedThreadId- {0} ", Thread.CurrentThread.ManagedThreadId));
_classificationsCacheLock.AcquireWriterLock(Timeout.Infinite); // can only set writer on or off on same thread, not between threads
if (_classificationsCache == null)
{
var cases = SomeServices.GetAllFOIRequests();
_classificationsCache = cases.SelectMany(c => c.Classifications.Classification.Select(cl => cl.Group)).Distinct().ToList();
}
}
catch (Exception ex)
{
_logger.Error(ex);
}
finally
{
if (_classificationsCacheLock.IsWriterLockHeld)
_classificationsCacheLock.ReleaseWriterLock();
}
}));
newThread.IsBackground = true;
newThread.Name = "MyNewThread"
newThread.Start();
Результат все тот же. classificationsCacheLock.AcquireReaderLock не ждет/блокирует, как кажется.
Я также добавил некоторые диагностические данные, чтобы проверить, если;
- Нить был фактически тот же поток, вы не можете ожидать R/W для блока на одной и той же нити
Экземпляр _classificationsCacheLock был идентичен во всех раз
общественного GetFOIRequestClassificationsList_Response GetFOIRequestClassificationsList() { попробовать {
GetFOIRequestClassificationsList_Response response = new GetFOIRequestClassificationsList_Response(); Debug.WriteLine(string.Format("GetFOIRequestClassificationsList - _classificationsCacheLock.GetHashCode - {0}", _classificationsCacheLock.GetHashCode())); Debug.WriteLine(string.Format("GetFOIRequestClassificationsList - Thread.CurrentThread.ManagedThreadId - {0} ", Thread.CurrentThread.ManagedThreadId)); Thread.Sleep(1000); _classificationsCacheLock.AcquireReaderLock(Timeout.Infinite); //_classificationsCacheMRE.WaitOne(); response.Classifications = _classificationsCache; _classificationsCacheLock.ReleaseReaderLock(); return response; } catch (Exception ex) { _logger.Error(ex); if (ex is FaultException) { throw; } else throw new FaultException(ex.Message); } }
Результат был ..
GetFOIRequestClassificationsList - _classificationsCacheLock.GetHashCode - 16265870
GetFOIRequestClassificationsList - Thread.CurrentThread.ManagedThreadId - 9
LoadCache - _classificationsCacheLock.GetHashCode - 16265870
LoadCache - Thread.CurrentThread.ManagedThreadId- 10
.. в таком порядке, так что теперь у нас есть условие гонки, как ожидается, так как блокировка записи была получена во вновь созданном потоке. Фактический вызов службы выполняется до того, как будет создан фактический запуск порождаемого потока конструктора.Поэтому я переношу
_classificationsCacheLock.AcquireWriterLock(Timeout.Infinite);
на конструктор, так как это гарантируется выполнение до того, как будут доступны какие-либо поля классов.
По-прежнему объект AcquireWriterLock не блокируется, несмотря на то, что конструктор был инициализирован в другом потоке WCF
потоку WCF
, который выполняет метод службы.
private void LoadCache()
{
_classificationsCacheLock.AcquireWriterLock(Timeout.Infinite);
Debug.WriteLine(string.Format("LoadCache constructor thread - _classificationsCacheLock.GetHashCode - {0}", _classificationsCacheLock.GetHashCode()));
Debug.WriteLine(string.Format("LoadCache constructor thread - Thread.CurrentThread.ManagedThreadId- {0} ", Thread.CurrentThread.ManagedThreadId));
var newThread = new Thread(new ThreadStart(() =>
{
try
{
Thread.Sleep(5000);
Debug.WriteLine(string.Format("LoadCache new thread - _classificationsCacheLock.GetHashCode - {0}", _classificationsCacheLock.GetHashCode()));
Debug.WriteLine(string.Format("LoadCache new thread - Thread.CurrentThread.ManagedThreadId- {0} ", Thread.CurrentThread.ManagedThreadId));
// _classificationsCacheLock.AcquireWriterLock(Timeout.Infinite); // can only set writer on or off on same thread, not between threads
if (_classificationsCache == null)
{
var cases = SomeServices.GetAllFOIRequests();
_classificationsCache = cases.SelectMany(c => c.Classifications.Classification.Select(cl => cl.Group)).Distinct().ToList();
}
}
catch (Exception ex)
{
_logger.Error(ex);
}
finally
{
if (_classificationsCacheLock.IsWriterLockHeld)
_classificationsCacheLock.ReleaseWriterLock();
}
}));
newThread.IsBackground = true;
newThread.Name = "CheckQueues" + DateTime.Now.Ticks.ToString();
newThread.Start();
}
Снова AcquireWriterLock не блокирует и не допускает присвоение нулевой ссылочной классификацииCache.
Результат был ..
LoadCache constructor thread - _classificationsCacheLock.GetHashCode - 22863715
LoadCache constructor thread - Thread.CurrentThread.ManagedThreadId- 9
GetFOIRequestClassificationsList - _classificationsCacheLock.GetHashCode - 22863715
GetFOIRequestClassificationsList - Thread.CurrentThread.ManagedThreadId - 8
LoadCache new thread - _classificationsCacheLock.GetHashCode - 22863715
LoadCache new thread - Thread.CurrentThread.ManagedThreadId- 10
EDIT 2
создал копию решения без контроля источника.
Загружено here, если вы хотите иметь проблемы с проблемой.
Изменено на использование события ручного перезапуска в коде для демонстрационных целей и закомментирован код проблемы.
- Разместите ваши контрольные точки
- запустить Example.Web в Debug
- в браузере перейдите к 'http://localhost:11164/GetFOIRequestClassificationsList.htm' и нажмите кнопку.
MRE works, ReaderWriterLock не работает должным образом.
.net 4.0 - C#
Печать '_classificationsCacheLock.GetHashCode()'. Кроме того, обычно лучше не полагаться на инстанцию WCF. Я не понимаю, почему это даже часть рамки. Поместите общее состояние в какой-либо другой класс и вручную управляйте им (что легко). – usr
Интересно и тревожно, что вы говорите, что у вас есть какие-либо связи с обсуждением таких проблем. Да, я немного разбираюсь в этом хэше экземпляра и вижу, действительно ли я нахожусь в какой-то проблеме управления контекстом объекта. – Terry
Работы по созданию WCF рекламируются, но зачем брать на себя хлопот понимания и тестирования? Это моя точка зрения. Создание одиночного самца тривиально. Зачем включать WCF? – usr