2015-05-15 6 views
4

У меня есть служба 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, если вы хотите иметь проблемы с проблемой.

Изменено на использование события ручного перезапуска в коде для демонстрационных целей и закомментирован код проблемы.

MRE works, ReaderWriterLock не работает должным образом.

.net 4.0 - C#

+0

Печать '_classificationsCacheLock.GetHashCode()'. Кроме того, обычно лучше не полагаться на инстанцию ​​WCF. Я не понимаю, почему это даже часть рамки. Поместите общее состояние в какой-либо другой класс и вручную управляйте им (что легко). – usr

+0

Интересно и тревожно, что вы говорите, что у вас есть какие-либо связи с обсуждением таких проблем. Да, я немного разбираюсь в этом хэше экземпляра и вижу, действительно ли я нахожусь в какой-то проблеме управления контекстом объекта. – Terry

+0

Работы по созданию WCF рекламируются, но зачем брать на себя хлопот понимания и тестирования? Это моя точка зрения. Создание одиночного самца тривиально. Зачем включать WCF? – usr

ответ

1

В методе CaseWork() вы создаете новый ReaderWriterLock каждый раз, когда метод вызывается. Таким образом, доступный замок просто выходит на Garbage COllector, и возникает новый. Поэтому никакой блокировки на самом деле не происходит правильно.

Почему вы не используете замок static для этого, создав его в конструкторе static?

Исправьте меня, если я ошибаюсь, но не могу, если вы обновляете кеш. Если это так, я предлагаю вам просто использовать класс Lazy<T>. Он потокобезопасен и содержит все считыватели до того, как значение будет установлено. Он внутренне использует TPL, и просто использовать:

private Lazy<List<string>> _classificationsCache = new Lazy<List<string>> 
(() => 
{ 
    var cases = SomeServices.GetAllFOIRequests(); 
    return cases.SelectMany(c => c.Classifications.Classification.Select(cl => cl.Group)).Distinct().ToList(); 
}); 

Вы можете получить значение, как это:

response.Classifications = _classificationsCache.Value; 

Update from MSDN:

Если текущий поток уже писатель блокировка, нет блокировки считывателя приобретено. Вместо этого счетчик блокировки при блокировке записи увеличивается на. Это предотвращает блокировку потока от его собственной блокировки записи. Результат в точности совпадает с вызовом AcquireWriterLock, а при освобождении блокировочного замка требуется дополнительный вызов ReleaseWriterLock.

Я думаю, что эта вещь произойдет:

Ваш замок читателя aquiring стреляют внутри же потоке (Task.StartNew метод использует свойство TaskScheduler.Current) замок писатель работает, так что это не так как он имеет те же привилегии, что и Task, и получил пустой список. Поэтому в ваших обстоятельствах вам нужно выбрать еще один примитив синхронизации.

+0

Я должен извиниться, я пропустил переименование исходного имени конструктора, теперь я исправил это. Это заставляет вас думать, что я каждый раз создаю новые экземпляры блокировки. только один экземпляр для экземпляра класса, который установлен в одиночный, поэтому WCF должен создать только один экземпляр MyService, который должен обслуживать каждый запрос. – Terry

+0

Действительно, я мог бы использовать поточно-безопасную коллекцию, и, возможно, это то, в этом конкретном случае. Но как странно, что ReaderWriterLock, похоже, ошибочно работает в этом сценарии. Я пока не вижу, что я сделал (или не сделал), что могло бы вызвать это. – Terry

+0

Я думаю, что «Задача», которую вы запускаете, не работает правильно. – VMAtm