Есть ли способ обнаружить, что какой-либо метод в моем коде вызывается без какой-либо блокировки в любом из методов ниже в стеке вызовов?
Цель состоит в том, чтобы отладить ошибочное приложение и выяснить, не являются ли определенные фрагменты кода потоковыми.
Обнаружение того, что метод вызывается без блокировки
Ответ 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;
}
}
Теперь часть регистрации. Я не уверен, что вам действительно нужно, чтобы это было беззаботным, коротких замков могло быть достаточно, но продолжайте думать, что делаете.
Я не знаю многих инструментов на С#, которые поддерживают операции блокировки, но самая простая версия этого я могу увидеть, используя 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);
}
}
Взаимозависимая магия магического регистра использует для обеспечения безопасной работы потоков (Compare-And-Swap, я считаю, но я действительно не знаю). Если вам нужен больше контекста, чем просто "Это случилось". Вы можете использовать параллельный стек или параллельную очередь, которые блокируются (они также используют блокировку: https://msdn.microsoft.com/en-us/library/dd997305.aspx/). Я бы включил в них временную метку, так как я не использовал их достаточно, чтобы знать, обещали ли они вернуть элементы в том порядке, в котором они произошли.
Как я уже сказал выше, вам может не потребоваться блокировка операций, но это должно быть. Я не знаю, подходит ли это для вас, поскольку я не знаю вашей конкретной проблемы, но она должна предоставить вам некоторые инструменты для решения этой проблемы.
Ответ 2
Вы можете разместить CLR самостоятельно и отслеживать блокировки, сделанные с помощью IHostSyncManager:: CreateMonitorEvent. Затем вам нужно будет разоблачить свой собственный механизм от вашего хоста до вашего метода, называемого "IsLockTaken()". Затем вы можете вызвать это из своего метода в своем фактическом коде.
Я думаю, что это возможно, но это будет довольно много работы и почти наверняка полное отвлечение от проблемы, которую вы пытаетесь решить, но, без сомнения, очень весело!
Здесь интересное чтение о обнаружении тупика https://blogs.msdn.microsoft.com/sqlclr/2006/07/25/deadlock-detection-in-sql-clr/