Есть ли способ обнаружить, что какой-либо метод в моем коде вызывается без какой-либо блокировки в любом из методов ниже в стеке вызовов?
Целью является отладка неисправного приложения и выяснение того, не являются ли определенные фрагменты кода потокобезопасными.Обнаружение того, что метод вызывается без блокировки
ответ
Это похоже на достойный вариант использования 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/). Я бы включил в них временную метку, так как я не использовал их достаточно, чтобы знать, обещали ли они вернуть элементы в том порядке, в котором они произошли.
Как я уже сказал выше, вам может НЕ НУЖНО блокировать операции, но это необходимо. Я не знаю, подходит ли это для вас, поскольку я не знаю вашей конкретной проблемы, но она должна предоставить вам некоторые инструменты для решения этой проблемы.
Да, 'Interlocked.Increment' использует операцию атомного сравнения и замены. – Georg
Вы могли бы host the CLR yourself и отслеживать блокировки, используя метод IHostSyncManager::CreateMonitorEvent. Затем вам нужно будет разоблачить свой собственный механизм от вашего хоста до вашего метода, называемого «IsLockTaken()». Затем вы можете вызвать это из своего метода в своем фактическом коде.
Я думаю, что это возможно, но это будет довольно много работы и почти наверняка полное отвлечение от проблемы, которую вы пытаетесь решить, но, без сомнения, очень весело!
Вот интересно читать об обнаружении ТУПИК https://blogs.msdn.microsoft.com/sqlclr/2006/07/25/deadlock-detection-in-sql-clr/
ReSharper находит ситуации, когда переменная член иногда используется внутри замка, а иногда и снаружи. –
@ Бернхард Хиллер, способности Решара в этой области очень, очень ограничены. Он основан на статическом анализе, который ловит только ограниченное подмножество случаев. – user626528
Вы хотите иметь возможность делать это без каких-либо изменений, используя только обычные инструкции «lock (object)»? Я могу представить, что вы можете сделать это, используя некоторые обертки объектов, которые в режиме отладки предоставят вам необходимую информацию. – Evk