Есть ли способ вызвать этот геттер и последовательно получить ожидаемый результат?

Мне нужно использовать получатель свойств из стороннего API, и просто доступ к этому получателю иногда приводит к зависанию всего приложения, иногда работает (в dev/debugger). И если я развертываю его на производственном сервере, хост-приложение иногда работает как положено, иногда умирает с APPCRASH в KERNEL32.

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

Это схема/шаблон, с которым я столкнулся:

internal MyClass() : ISomeInterface
{
    Locker = new object();
}

public object Locker { get; private set; }

private SomeObject _myProperty;
public SomeObject MyProperty
{
    get
    {
        lock (Locker)
        {
            if (_myProperty== null)
                MyProperty = SomeCondition ? SomeMethod() : Something.Else;
            return _myProperty;
        }
    }

    private set
    {
        _myProperty = value;
    }
}

Правильно ли я считаю, что всякий раз, когда _myProperty равно null, геттер вызывает сеттер, и здесь есть ошибка состояния гонки, которая может вызывать случайное/случайное зависание, которое я испытываю? Как я могу подтвердить/подтвердить, что это замораживание фактически является тупиком?

Какой будет правильная реализация? Меня заставили поверить, что если бы у установщика также был lock (Locker) (мне тоже не нравится, что Locker является общедоступным, звучит как напрашиваться на неприятности), и получатель назначал бы _myProperty вместо вызова установщика, это было бы потокобезопасный.

Наконец, есть ли способ вызвать этот геттер, не требуя от третьей стороны отправки «исправленной» версии и без зависания?

Я пытался вызывать его из основного потока пользовательского интерфейса, я пытался вызывать его из Task/фонового потока, я пробовал Sleep(200) перед вызовом, я пытался установить тайм-аут для Task... независимо от того, что я делаю, Кажется, я не могу постоянно вызывать этот геттер и надежно получать ожидаемый результат (это действительно работает... иногда).

Объект Locker является общедоступным, но он определен в internal class, к которому я обращаюсь только через его интерфейс, который, конечно же, не раскрывает его. Есть ли способ использовать отражение, чтобы получить блокировку извне (Monitor.TryEnter(magicallyObtainedLocker)) и заставить ее работать? Или этот геттер полностью borked?


person Mathieu Guindon    schedule 31.01.2014    source источник
comment
У меня нет ответа для вас, но я просто хотел сказать ВАУ! к этой комбинации геттер/сеттер. Это просто... ужасно!   -  person Adam Modlin    schedule 01.02.2014
comment
Есть причина, по которой @retailcoder является любимым пользователем Code Review — он не хочет иметь дело с таким кодом!   -  person Simon Forsberg    schedule 01.02.2014
comment
К сожалению, я думаю, что лучше всего будет заставить третью сторону исправить свою библиотеку :(   -  person Reed Copsey    schedule 01.02.2014
comment
Используйте свой любимый декомпилятор, чтобы выяснить, кто еще использует этот шкафчик!   -  person toATwork    schedule 01.02.2014


Ответы (2)


Правильно ли я считаю, что всякий раз, когда _myProperty имеет значение null, геттер вызывает сеттер, и здесь есть ошибка состояния гонки, которая может вызывать случайное/случайное зависание, которое я испытываю? Как я могу подтвердить/подтвердить, что это замораживание фактически зашло в тупик?

Нет. В C# lock является реентерабельным для одного и того же потока. Вы можете увидеть это в документации для Monitor.Enter:

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


мне тоже не нравится, что шкафчик общедоступен, звучит так, как будто напрашивается на неприятности

Я согласен с вами здесь. Это скорее всего виновник. Тот факт, что Locker является общедоступным, скорее всего, означает, что что-то еще в библиотеке блокирует этот объект, что может быть причиной взаимоблокировки.

Если вы можете запустить это через Visual Studio Concurrency Profiler, вы сможете приостановить работу в тупиковой ситуации и увидеть блокировку объектов в этот момент времени.

person Reed Copsey    schedule 31.01.2014
comment
Найти использование на Locker дает мало результатов, все в геттерах, настроенных таким образом, все в одном классе; этот геттер - единственный, который вызывает сеттер. Вроде публичный со штампом ЯГНИ... - person Mathieu Guindon; 01.02.2014
comment
@retailcoder Тот факт, что вы получаете сбой приложения, предполагает, что это, вероятно, проблема в SomeMethod или одном из свойств, к которым обращаются в геттере, на самом деле ... - person Reed Copsey; 01.02.2014
comment
Значит, это не может быть связано ни со свойством играющим само с собой, ни с многопоточностью? - person Mathieu Guindon; 01.02.2014
comment
@retailcoder Недвижимость, играющая сама с собой, — это нормально. Взаимная блокировка, вероятно, приведет к другому поведению, так как это просто приведет к бесконечному зависанию... - person Reed Copsey; 01.02.2014

Есть ли способ использовать отражение, чтобы получить блокировку извне (Monitor.TryEnter(magicallyObtainedLocker)) и заставить ее работать?

Да, учитывая, что вы можете его декомпилировать, я бы предположил, что да1; следующее «работает на моей машине ':

  • «Сторонний» встроен в отдельный проект в виде DLL.
  • «InternalLocker» — это консольная программа в другом проекте, которая ссылается на стороннюю DLL.

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


using System;

namespace ThirdParty
{
    public interface ISomeInterface
    {
        string MyProperty { get; }
    }

    public static class Factory
    {
        public static ISomeInterface create() { return new MyClass(); }
    }

    internal class MyClass : ISomeInterface
    {
        internal MyClass()
        {
            Locker = new object();
        }

        public object Locker { get; private set; }

        private string _myProperty;
        public string MyProperty
        {
            get
            {
                lock (Locker)
                {
                    if (_myProperty == null)
                        MyProperty = "foo";
                    return _myProperty;
                }
            }

            private set
            {
                _myProperty = value;
            }
        }
    }
}

using System;
using System.Linq;

using System.Reflection;
using System.Threading;
using ThirdParty;

namespace InternalLocker
{
    class Program
    {
        static void Main(string[] args)
        {
            test1();
            test2();
        }

        static void test1()
        {
            ISomeInterface thing = Factory.create();
            string foo = thing.MyProperty;
            assert(foo == "foo");
        }

        static void test2()
        {
            ISomeInterface thing = Factory.create();
            // use reflection to find the public property
            Type type = thing.GetType();
            PropertyInfo propertyInfo = type.GetProperty("Locker");
            // get the property value
            object locker = propertyInfo.GetValue(thing, null);
            // use the property value
            if (Monitor.TryEnter(locker))
            {
                string foo = thing.MyProperty;
                Monitor.Exit(locker);
                assert(foo == "foo");
            }
            else
                assert(false);
        }

        static void assert(bool b)
        {
            if (!b)
                throw new Exception();
        }
    }
}
person ChrisW    schedule 31.01.2014