Возможно ли реализовать блокировку по объему в C #? - PullRequest
5 голосов
/ 02 ноября 2008

Обычный шаблон в C ++ - это создание класса, который обертывает блокировку - блокировка либо неявно берется при создании объекта, либо берется явно позже. Когда объект выходит из области видимости, dtor автоматически снимает блокировку. Возможно ли сделать это в C #? Насколько я понимаю, нет гарантий того, когда dtor в C # будет запущен после того, как объект выйдет из области видимости.

Пояснение: Любая блокировка вообще, спинлок, ReaderWriterLock, что угодно. Вызов Dispose сам побеждает цель шаблона - снять блокировку, как только мы выходим из области видимости - независимо от того, вызвали ли мы return в середине, исключение типа throw или что-то еще. Кроме того, насколько я понимаю, использование по-прежнему будет только объектом очереди для GC, а не уничтожать его немедленно ...

Ответы [ 6 ]

12 голосов
/ 02 ноября 2008

Чтобы усилить ответ Тимоти, оператор блокировки действительно создает блокировку по области действия с использованием монитора. По сути, это выглядит примерно так:

lock(_lockKey)
{
    // Code under lock
}

// is equivalent to this
Monitor.Enter(_lockKey)
try
{
     // Code under lock
}
finally
{
    Monitor.Exit(_lockKey)
}

В C # вы редко используете dtor для этого типа паттерна (см. Оператор using / IDisposable). В коде вы можете заметить одну вещь: если между Monitor.Enter и попыткой возникает асинхронное исключение, похоже, что монитор не будет освобожден. JIT фактически дает специальную гарантию, что если Monitor.Enter непосредственно предшествует блоку try, асинхронное исключение не произойдет, пока блок try не обеспечит освобождение.

5 голосов
/ 02 ноября 2008

Ваше понимание относительно using неверно, это способ, которым действия по определенным областям происходят детерминистическим образом (очереди в GC не создаются).

C # предоставляет ключевое слово lock, которое обеспечивает эксклюзивную блокировку, и если вы хотите использовать разные типы (например, чтение / запись), вам придется использовать оператор using.

P.S. Эта тема может вас заинтересовать.

4 голосов
/ 02 ноября 2008

Это правда, что вы точно не знаете, когда будет запущен dtor ... но, если вы реализуете интерфейс IDisposable, а затем используете либо блок «using», либо сами вызываете «Dispose ()», вы будет место для размещения вашего кода.

Вопрос: Когда вы говорите «блокировка», подразумеваете ли вы блокировку потока, чтобы объект мог использовать только один поток за раз? Как в:

lock (_myLockKey) { ... }

Пожалуйста, уточните.

3 голосов
/ 02 ноября 2008

Для полноты есть другой способ достижения аналогичного эффекта RAII без использования using и IDisposable. В C # using обычно яснее ( см. Также здесь , чтобы узнать больше), но в других языках (например, Java) или даже в C #, если using не подходит по какой-то причине, это полезно знать.

Это идиома, называемая «Execute Around», и идея заключается в том, что вы вызываете метод, который выполняет предварительные и последующие действия (например, блокирует / разблокирует ваши потоки, или устанавливает и фиксирует / закрывает ваше соединение с БД и т. Д.), И вы передайте в этот метод делегат, который будет выполнять операции, которые вы хотите выполнить между ними.

например:.


funkyObj.InOut( delegate{ System.Console.WriteLine( "middle bit" ); } );

В зависимости от того, что делает метод InOut, вывод может выглядеть примерно так:

first bit
middle bit
last bit

Как я уже сказал, этот ответ только для полноты, предыдущие предложения using с IDisposable, а также ключевое слово lock будут лучше в 99% случаев.

Обидно, что, хотя .Net пошел дальше, чем многие другие современные ОО-языки в этом отношении (я смотрю на вас, Java), он все же возлагает ответственность за RAII на работу над клиентским кодом (т.е. код, который использует using), тогда как в C ++ деструктор всегда будет запускаться в конце области.

0 голосов
/ 03 апреля 2019

Зачем вам в первую очередь блокируемый замок? Предположим, у вас есть следующий код:

lock(obj) {
    ... some logic goes here
}

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

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

Джеффри Рихтер специально советует не использовать lock выражение.

0 голосов
/ 03 декабря 2008

Меня по-настоящему беспокоит тот факт, что using зависит от разработчика, который должен помнить - в лучшем случае вы получите предупреждение, которое большинство людей никогда не удосуживается привести к ошибке. Итак, я играю с такой идеей - она ​​заставляет клиента, по крайней мере, пытаться сделать все правильно. К счастью и к сожалению, это закрытие, поэтому клиент все еще может сохранить копию ресурса и попытаться использовать его позже, но этот код по крайней мере пытается подтолкнуть клиента в правильном направлении ...

public class MyLockedResource : IDisposable
{
    private MyLockedResource()
    {
        Console.WriteLine("initialize");
    }

    public void Dispose()
    {
        Console.WriteLine("dispose");
    }

    public delegate void RAII(MyLockedResource resource);

    static public void Use(RAII raii)
    {
        using (MyLockedResource resource = new MyLockedResource())
        {
            raii(resource);
        }
    }

    public void test()
    {
        Console.WriteLine("test");
    }
}

Хорошее использование:

MyLockedResource.Use(delegate(MyLockedResource resource)
{
    resource.test();
});

Неправильное использование! (К сожалению, это не может быть предотвращено ...)

MyLockedResource res = null;
MyLockedResource.Use(delegate(MyLockedResource resource)
{
    resource.test();
    res = resource;
    res.test();
});
res.test();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...