Подтвердить что ты не робот

Обнаружение того, что метод вызывается без блокировки

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

4b9b3361

Ответ 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/