2016-10-21 10 views
13

Есть ли способ обнаружить, что какой-либо метод в моем коде вызывается без какой-либо блокировки в любом из методов ниже в стеке вызовов?
Целью является отладка неисправного приложения и выяснение того, не являются ли определенные фрагменты кода потокобезопасными.Обнаружение того, что метод вызывается без блокировки

+0

ReSharper находит ситуации, когда переменная член иногда используется внутри замка, а иногда и снаружи. –

+0

@ Бернхард Хиллер, способности Решара в этой области очень, очень ограничены. Он основан на статическом анализе, который ловит только ограниченное подмножество случаев. – user626528

+0

Вы хотите иметь возможность делать это без каких-либо изменений, используя только обычные инструкции «lock (object)»? Я могу представить, что вы можете сделать это, используя некоторые обертки объектов, которые в режиме отладки предоставят вам необходимую информацию. – Evk

ответ

1

Это похоже на достойный вариант использования AOP (аспектно-ориентированное программирование). Очень основное резюме АОП заключается в том, что его метод борьбы с перекрестными проблемами заключается в том, чтобы сделать код сухим и модульным. Идея состоит в том, что если вы делаете что-то при каждом вызове метода на объекте (например, записываете в журнал каждый вызов) вместо добавления журнала в начале и в конце каждого метода, вы вместо этого наследуете объект и выполняете это за пределами класса чтобы не мутить его цели.

Это можно сделать несколькими способами, и я приведу вам пример из двух. Сначала это вручную (это не очень удобно, но можно сделать очень легко для маленьких кассет).

Предположим, у вас есть класс, Doer с двумя методами Do and Other. Вы можете наследовать от этого и сделать

public class Doer 
{ 
    public virtual void Do() 
    { 
     //do stuff. 
    } 

    public virtual void Other() 
    { 
     //do stuff. 
    } 
} 

public class AspectDoer : Doer 
{ 
    public override void Do() 
    { 
     LogCall("Do"); 
     base.Do(); 
    } 

    public override void Other() 
    { 
     LogCall("Other"); 
     base.Other(); 
    } 

    private void LogCall(string method) 
    { 
     //Record call 
    } 
} 

Это замечательно, если вы заботитесь только о один класс, но быстро становится неосуществимым, если вы должны сделать это для многих классов. В таких случаях я бы рекомендовал использовать что-то вроде библиотеки CastleProxy. Это библиотека, которая динамически создает прокси для переноса любого класса, который вы хотите. В сочетании с IOC вы можете легко обернуть каждую услугу в своем приложении.

Вот краткий пример использования CastleProxy, основные моменты быть использование ProxyGenerator.GenerateProxy и передать в IInterceptors делать вещи вокруг вызовов метода:

[Test] 
    public void TestProxy() 
    { 
     var generator = new ProxyGenerator(); 
     var proxy = generator.CreateClassProxy<Doer>(new LogInterceptor()); 
     proxy.Do(); 
     Assert.True(_wasCalled); 
    } 

    private static bool _wasCalled = false; 
    public class LogInterceptor : IInterceptor 
    { 
     public void Intercept(IInvocation invocation) 
     { 
      Log(invocation.Method.Name); 
      invocation.Proceed(); 
     } 

     private void Log(string name) 
     { 
      _wasCalled = true; 
     } 
    } 

Теперь, лесозаготовительной часть. Я не уверен, что вам действительно нужно, чтобы это было заблокировано, коротких замков могло быть достаточно, но вы можете продолжать думать, что делаете.

Я не знаю многих инструментов на C#, которые поддерживают операции блокировки, но самая простая версия этого я могу видеть, используя Interlocked, чтобы увеличить счетчик количества экземпляров в методе в любой момент времени. Если бы выглядеть примерно так:

 [Test] 
    public void TestProxy() 
    { 
     var generator = new ProxyGenerator(); 
     var proxy = generator.CreateClassProxy<Doer>(new LogInterceptor()); 
     proxy.Do(); 
     Assert.AreEqual(1, _totalDoCount); 
    } 

    private static int _currentDoCount = 0; 
    private static int _totalDoCount = 0; 
    public class LogInterceptor : IInterceptor 
    { 
     public void Intercept(IInvocation invocation) 
     { 
      if (invocation.Method.Name == "Do") 
      { 
       var result = Interlocked.Increment(ref _currentDoCount); 
       Interlocked.Increment(ref _totalDoCount); 
       if(result > 1) throw new Exception("thread safe violation"); 
      } 


      invocation.Proceed(); 
      Interlocked.Decrement(ref _currentDoCount); 
     } 
    } 

Interlocked использует волшебную магию регистра, чтобы сделать нить безопасной работы (сравнение с обменом я верю, но я не знаю). Если вам нужен больше контекста, чем просто «Это случилось». Вы можете использовать параллельный стек или параллельную очередь, которые являются незаблокированными (они также используют блокировку: https://msdn.microsoft.com/en-us/library/dd997305.aspx/). Я бы включил в них временную метку, так как я не использовал их достаточно, чтобы знать, обещали ли они вернуть элементы в том порядке, в котором они произошли.

Как я уже сказал выше, вам может НЕ НУЖНО блокировать операции, но это необходимо. Я не знаю, подходит ли это для вас, поскольку я не знаю вашей конкретной проблемы, но она должна предоставить вам некоторые инструменты для решения этой проблемы.

+0

Да, 'Interlocked.Increment' использует операцию атомного сравнения и замены. – Georg

0

Вы могли бы host the CLR yourself и отслеживать блокировки, используя метод IHostSyncManager::CreateMonitorEvent. Затем вам нужно будет разоблачить свой собственный механизм от вашего хоста до вашего метода, называемого «IsLockTaken()». Затем вы можете вызвать это из своего метода в своем фактическом коде.

Я думаю, что это возможно, но это будет довольно много работы и почти наверняка полное отвлечение от проблемы, которую вы пытаетесь решить, но, без сомнения, очень весело!

Вот интересно читать об обнаружении ТУПИК https://blogs.msdn.microsoft.com/sqlclr/2006/07/25/deadlock-detection-in-sql-clr/