Вопрос

Мне нужно сделать существующий поток приложения безопасным.В силу обстоятельств (см. ниже) я решил использовать один единственный ReaderWriterLock для всего графа бизнес-объектов.Все методы/свойства должны выглядеть следующим образом:

public int MyReadOperation(string inputParam)
{
   rwLock.AcquireReaderLock(10000);
   try
   {
      // do all read operations
      ...
   }
   finally
   {
      rwLock.ReleaseReaderLock();
   }
}

public void MyWriteOperation(string input)
{
   rwLock.AcquireWriterLock(10000);
   try
   {
      // do all write operations
      ...
   }
   finally
   {
      rwLock.ReleaseWriterLock();
   }

}

Но мне нужно охватить огромное количество методов, и меня пугает идея копирования/вставки.Вдохновленный MethodImplAttribute, я бы предпочел иметь такой код, который бы вел себя как код выше:

[ReadOperation]
public int MyReadOperation(string inputParam)
{
   // do all read operations
   ...
}

[WriteOperation]    
public void MyWriteOperation(string input)
{
   // do all write operations
   ...
}

Есть ли способ прервать выполнение потока до/после входа в свойство или метод и добавления мер безопасности потоков?Или каким-то образом использовать возможности функционального языка C#, встраивая продуктивное тело методов в общий ReaderWriterLock, получающий «фрейм»?

Немного предыстории:

Я работаю над проектом, в котором бизнес-объекты носителя данных предоставляются через .NET Remoting.Однако эти классы данных не сериализуемы, а MarshalByRef-s.Это означает, что ВСЕ клиенты фактически читают/записывают одни и те же бизнес-объекты.Этого нельзя изменить, это высечено в камне.Надежда на безопасность потоков состоит в том, что эти удаленные бизнес-объекты доступны только для чтения в глазах удаленных клиентов (хотя они зацикливают множество списков), и все операции записи красиво разделены в выделенный фасад.Я ожидаю редких записей и частого чтения.Бизнес-объекты тесно связаны, они очень «графичны».

Это было полезно?

Решение 2

Прежде всего, спасибо Эндрю, который указал мне на ПостШарп.Основываясь на его ответе, я пришел к этому окончательному выводу.

[Serializable]
public class ReadOperationAttribute : PostSharp.Laos.OnMethodInvocationAspect
{
    public override void  OnInvocation(MethodInvocationEventArgs eventArgs)
    {
        ReaderWriterLock rwLock = GetLock();
        rwLock.AcquireReaderLock(10000);
        try { eventArgs.Proceed(); }
        finally { rwLock.ReleaseReaderLock();}
    }
}
public class Foo
{
    [ReadOperation]
    public string Bar
    {

        get { return "stuff"; }
        set { Console.WriteLine(value); }
    }

    [ReadOperation]
    public void Test(string input)
    {
        Console.WriteLine(input);
    }
}

Он делает именно то, что я выразил в вопросе, и прекрасно отлаживается.Мы можем сделать его еще более полезным, если объявим атрибут для всей сборки:

[assembly: ReadOperation(AttributeTargetElements=MulticastTargets.Method,
AttributeTargetTypes="MyNamespace.*")]

Таким образом, нам не нужно декорировать каждый метод/свойство, но, поместив этот атрибут в сборку один раз, мы блокируем все наши методы/свойства с помощью ReaderWriterLock.

Кроме того, как отметил Эндрю, Microsoft рекомендует использовать ReaderWriterLockSlim, если вы используете .NET3.5 или выше.

Другие советы

Я бы сделал что-то подобное, используя ПостШарп.

Он запускается как дополнительный этап сборки и вставляет соответствующий MSIL, но будет делать то, что вы хотите.Определения атрибутов PostSharp будут выглядеть примерно так (в зависимости от вашей конкретной реализации).Затем их можно использовать, как вы описали выше.

public sealed class ReadOperationAttribute : OnMethodBoundaryAspect
{
  // not quite sure how you manage your lock, so put this dummy method in.
  ReaderWriterLock _rwLock = ReaderWriterLock.GetCorrectReaderWriterLock();

  [ThreadStatic]
  static bool _isLocked;

  public override void OnEntry( MethodExecutionEventArgs e )
  {
    try
    {
        _rwLock.AcquireReaderLock(10000);
        _isLocked = true;
    }
    catch
    {
        _isLocked = false;
        throw;
    }
  } 

  public override void OnExit( MethodExecutionEventArgs e )
  {    
      if (_isLocked) 
      {
          _rwLock.ReleaseReaderLock();
          _isLocked = false;
      }
  } 
}

public sealed class WriteOperationAttribute : OnMethodBoundaryAspect
{
  // not quite sure how you manage your lock, so put this dummy method in.
  ReaderWriterLock _rwLock = ReaderWriterLock.GetCorrectReaderWriterLock();

  [ThreadStatic]
  static bool _isLocked;

  public override void OnEntry( MethodExecutionEventArgs e )
  {
     try
    {
        _rwLock.AcquireWriterLock(10000);
        _isLocked = true;
    }
    catch
    {
        _isLocked = false;
        throw;
    }
  } 

  public override void OnExit( MethodExecutionEventArgs e )
  {
      if (_isLocked) 
      {
          _rwLock.ReleaseReaderLock();
          _isLocked = false;
      }
  } 
}

РЕДАКТИРОВАТЬ:Обновлено для устранения проблем.(Непроверенные;)) также, обратите внимание, как я сказал в комментарии к вопросу, Microsoft рекомендует с использованием ReaderWriterLockSlim в предпочтении ReaderWriterLock.

Использование этапа после сборки может быть хорошим решением.В чистом коде я бы создал методы расширения ReaderWriterLock следующим образом:

public static void ReadLock(this ReaderWriterLock l, Action f) {
  l.AquireReaderLock();
  try { f(); }
  finally { l.ReleaseReaderLock(); }
}

И один для записи.Если блокировка действительно глобальная, вы даже можете создать статические методы.Тогда ваш код будет выглядеть намного менее раздражающим:

public void DoSomething() {
  theLock.ReadLock( () => {
  // Existing code
  });
}

Преимущество этого метода в том, что он является детальным, поэтому части метода, которые не нуждаются в синхронизации, не будут синхронизированы.Но это все равно требует внесения изменений в каждый метод.К счастью, они относительно небольшие, и весь код блокировки, который вам придется изменить, находится как минимум в одном месте.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top