'using' statement vs 'try finally'

У меня есть куча свойств, которые я буду использовать для блокировки чтения/записи. Я могу реализовать их либо с предложением try finally, либо using.

В try finally я бы приобрел блокировку перед try и выпустил в finally. В предложении using я бы создал класс, который получает блокировку в своем конструкторе и выпускает его в методе Dispose.

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

Способ 1 (try finally):

static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
private DateTime dtMyDateTime_m
public DateTime MyDateTime
{
    get
    {
        rwlMyLock_m .AcquireReaderLock(0);
        try
        {
            return dtMyDateTime_m
        }
        finally
        {
            rwlMyLock_m .ReleaseReaderLock();
        }
    }
    set
    {
        rwlMyLock_m .AcquireWriterLock(0);
        try
        {
            dtMyDateTime_m = value;
        }
        finally
        {
            rwlMyLock_m .ReleaseWriterLock();
        }
    }
}

Способ 2:

static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
private DateTime dtMyDateTime_m
public DateTime MyDateTime
{
    get
    {
        using (new ReadLock(rwlMyLock_m))
        {
            return dtMyDateTime_m;
        }
    }
    set
    {
        using (new WriteLock(rwlMyLock_m))
        {
            dtMyDateTime_m = value;
        }
    }
}

public class ReadLock : IDisposable
{
    private ReaderWriterLock rwl;
    public ReadLock(ReaderWriterLock rwl)
    {
        this.rwl = rwl;
        rwl.AcquireReaderLock(0);
    }

    public void Dispose()
    {
        rwl.ReleaseReaderLock();
    }
}

public class WriteLock : IDisposable
{
    private ReaderWriterLock rwl;
    public WriteLock(ReaderWriterLock rwl)
    {
        this.rwl = rwl;
        rwl.AcquireWriterLock(0);
    }

    public void Dispose()
    {
        rwl.ReleaseWriterLock();
    }
}

Ответ 1

Из MSDN, using Statement (ссылка на С#)

Оператор using гарантирует, что Dispose вызывается, даже если возникает исключение, когда вы вызываете методы на объекте. Вы можете добиться того же результата, поставив объект внутри блока try и затем вызывая Dispose в блоке finally; Фактически, это то, как оператор using преобразуется компилятором. Пример кода ранее расширяется до следующего кода во время компиляции (обратите внимание на дополнительные фигурные скобки для создания ограниченной области для объекта):

{
  Font font1 = new Font("Arial", 10.0f);
  try
  {
    byte charset = font1.GdiCharSet;
  }
  finally
  {
    if (font1 != null)
      ((IDisposable)font1).Dispose();
  }
}

Таким образом, в основном это тот же код, но с хорошими автоматическими проверками нуль и дополнительной областью для вашей переменной. В документации также указано, что она "обеспечивает правильное использование объекта IDisposable", поэтому вы можете получить еще лучшую поддержку фреймворка для любых неясных случаев в будущем.

Итак, идите с опцией 2.

Наличие переменной внутри области действия, которая заканчивается сразу же после ее отсутствия, также является плюсом.

Ответ 2

Я определенно предпочитаю второй метод. Это более красноречиво в точке использования и меньше подвержено ошибкам.

В первом случае кто-то, редактируя код, должен быть осторожным, чтобы не вставлять ничего между вызовом Acquire (Read | Write) Lock и try.

(Использование блокировки чтения/записи для отдельных свойств, таких как это, как правило, слишком велико. Лучше всего применять их на гораздо более высоком уровне. Часто бывает достаточно простого блокирования, поскольку вероятность спора, по-видимому, очень мала, учитывая время, блокировка удерживается, и приобретение блокировки чтения/записи является более дорогостоящей операцией, чем простая блокировка).

Ответ 3

Учесть, что оба решения плохи, потому что они маскируют исключения.

A try без catch, очевидно, будет плохой идеей; см. MSDN, почему оператор using также опасен.

Примечание. Корпорация Майкрософт теперь рекомендует ReaderWriterLockSlim вместо ReaderWriterLock.

Наконец, обратите внимание, что в примерах Microsoft используются два блока try-catch, чтобы избежать этих проблем, например.

try
{
    try
    {
         //Reader-writer lock stuff
    }
    finally
    {
         //Release lock
    }
 }
 catch(Exception ex)
 {
    //Do something with exception
 }

Простое, последовательное, чистое решение - хорошая цель, но если вы не можете просто использовать lock(this){return mydateetc;}, вы можете пересмотреть подход; с дополнительной информацией Я уверен, что Qaru может помочь; -)

Ответ 4

Я лично использую оператор С# "using" как можно чаще, но есть несколько конкретных вещей, которые я делаю вместе с ним, чтобы избежать упомянутых потенциальных проблем. Чтобы проиллюстрировать:

void doSomething()
{
    using (CustomResource aResource = new CustomResource())
    {
        using (CustomThingy aThingy = new CustomThingy(aResource))
        {
            doSomething(aThingy);
        }
    }
}

void doSomething(CustomThingy theThingy)
{
    try
    {
        // play with theThingy, which might result in exceptions
    }
    catch (SomeException aException)
    {
        // resolve aException somehow
    }
}

Обратите внимание, что я разделяю оператор "using" на один метод и использование объекта (ов) в другой метод с блоком "try" / "catch". Я могу вложить несколько "используемых" утверждений, подобных этому для связанных объектов (иногда я делаю три или четыре в моем производственном коде).

В моих методах Dispose() для этих пользовательских классов IDisposable я улавливаю исключения (но НЕ ошибки) и регистрирую их (используя Log4net). Я никогда не сталкивался с ситуацией, когда любое из этих исключений могло повлиять на мою обработку. Потенциальным ошибкам, как обычно, разрешено распространять стек вызовов и, как правило, завершать обработку соответствующим сообщением (трассировкой ошибок и стека).

Если я каким-то образом столкнулся с ситуацией, когда во время Dispose() произошло значительное исключение, я бы пересмотрел эту ситуацию. Честно говоря, я сомневаюсь, что это произойдет.

Между тем, преимущества и преимущества очистки "использования" делают его одним из моих самых любимых функций С#. Кстати, я работаю на Java, С# и Python в качестве моих основных языков, а также множество других, которые были добавлены здесь и там, а "использование" - одна из моих самых любимых функций языка, потому что это практичная повседневная рабочая лошадка.

Ответ 5

DRY говорит: второе решение. Первое решение дублирует логику использования блокировки, тогда как вторая не делает.

Ответ 6

Мне нравится третий вариант

private object _myDateTimeLock = new object();
private DateTime _myDateTime;

public DateTime MyDateTime{
  get{
    lock(_myDateTimeLock){return _myDateTime;}
  }
  set{
    lock(_myDateTimeLock){_myDateTime = value;}
  }
}

Из двух ваших опций второй вариант - самый чистый и более простой для понимания, что происходит.

Ответ 7

"Букет свойств" и блокировка на уровне получателя и уровня сеттера выглядит неправильно. Ваша блокировка слишком тонкая. В большинстве типичных объектов вы должны убедиться, что вы приобрели блокировку для доступа к нескольким свойствам одновременно. Ваш конкретный случай может быть другим, но я сомневаюсь в этом.

Во всяком случае, приобретение блокировки при доступе к объекту вместо свойства значительно сократит количество кода блокировки, который вам придется писать.

Ответ 8

Блоки Try/Catch, как правило, предназначены для обработки исключений, а использование блоков используется для обеспечения того, чтобы объект был удален.

Для блокировки чтения/записи попытка try/catch может быть наиболее полезной, но вы также можете использовать оба способа:

using (obj)
{
  try { }
  catch { }
}

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

Ответ 9

Я думаю, что метод 2 будет лучше.

  • Более простой и понятный код в ваших свойствах.
  • Меньше ошибок, поскольку код блокировки не нужно переписывать несколько раз.

Ответ 10

Хотя я согласен со многими из приведенных выше замечаний, включая гранулярность блокировки и сомнительную обработку исключений, вопрос является одним из подходов. Позвольте мне дать вам одну большую причину, почему я предпочитаю использовать над try {} окончательно модель... абстракция.

У меня есть модель, очень похожая на вашу, за одним исключением. Я определил базовый интерфейс ILock, и в нем я предоставил один метод под названием Acquire(). Метод Acquire() возвратил объект IDisposable и в результате означает, что до тех пор, пока объект, с которым я имею дело, имеет тип ILock, который может использоваться для выполнения области блокировки. Почему это важно?

Мы имеем дело со многими различными механизмами блокировки и поведением. У объекта блокировки может быть определенный тайм-аут, который используется. Реализация блокировки может быть блокировкой монитора, блокировкой считывателя, блокировкой записи или блокировкой блокировки. Однако, с точки зрения вызывающего, все это не имеет значения, что их волнует, так это то, что контракт на блокировку ресурса соблюдается и что блокировка делает это в соответствии с его реализацией.

interface ILock {
    IDisposable Acquire();
}

class MonitorLock : ILock {
    IDisposable Acquire() { ... acquire the lock for real ... }
}

Мне нравится ваша модель, но я бы подумал о сокрытии механики блокировки от вызывающего. FWIW, я измерил накладные расходы на использование техники по сравнению с try-finally, и накладные расходы на выделение одноразового объекта будут иметь от 2 до 3% рабочих расходов.

Ответ 11

Я удивлен, что никто не предложил инкапсулировать try-finally в анонимные функции. Точно так же, как метод создания экземпляров и использования классов с помощью инструкции using, это сохраняет блокировку в одном месте. Я предпочитаю это сам только потому, что лучше прочитал слово "наконец", чем слово "Dispose", когда я собираюсь снять блокировку.

class StackOTest
{
    private delegate DateTime ReadLockMethod();
    private delegate void WriteLockMethod();

    static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
    private DateTime dtMyDateTime_m;
    public DateTime MyDateTime
    {
        get
        {
            return ReadLockedMethod(
                rwlMyLock_m,
                delegate () { return dtMyDateTime_m; }
            );
        }
        set
        {
            WriteLockedMethod(
                rwlMyLock_m,
                delegate () { dtMyDateTime_m = value; }
            );
        }
    }

    private static DateTime ReadLockedMethod(
        ReaderWriterLock rwl,
        ReadLockMethod method
    )
    {
        rwl.AcquireReaderLock(0);
        try
        {
            return method();
        }
        finally
        {
            rwl.ReleaseReaderLock();
        }
    }

    private static void WriteLockedMethod(
        ReaderWriterLock rwl,
        WriteLockMethod method
    )
    {
        rwl.AcquireWriterLock(0);
        try
        {
            method();
        }
        finally
        {
            rwl.ReleaseWriterLock();
        }
    }
}

Ответ 12

SoftwareJedi, у меня нет учетной записи, поэтому я не могу редактировать свои ответы.

В любом случае предыдущая версия была не очень хороша для общего использования, поскольку для блокировки чтения всегда требовалось возвращаемое значение. Это исправляет:

class StackOTest
{
    static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
    private DateTime dtMyDateTime_m;
    public DateTime MyDateTime
    {
        get
        {
            DateTime retval = default(DateTime);
            ReadLockedMethod(
                delegate () { retval = dtMyDateTime_m; }
            );
            return retval;
        }
        set
        {
            WriteLockedMethod(
                delegate () { dtMyDateTime_m = value; }
            );
        }
    }

    private void ReadLockedMethod(Action method)
    {
        rwlMyLock_m.AcquireReaderLock(0);
        try
        {
            method();
        }
        finally
        {
            rwlMyLock_m.ReleaseReaderLock();
        }
    }

    private void WriteLockedMethod(Action method)
    {
        rwlMyLock_m.AcquireWriterLock(0);
        try
        {
            method();
        }
        finally
        {
            rwlMyLock_m.ReleaseWriterLock();
        }
    }
}

Ответ 13

На самом деле, в вашем первом примере, чтобы сделать решения сопоставимыми, вы бы также применили IDisposable там. Затем вы будете вызывать Dispose() из блока finally вместо того, чтобы снимать блокировку напрямую.

Тогда вы будете внедрением "яблоки к яблокам" (и MSIL) -wise (MSIL будет одинаковым для обоих решений). Возможно, по-прежнему будет хорошей идеей использовать using из-за добавленной области видимости и потому, что Framework обеспечит правильное использование IDisposable (последнее менее полезно, если вы сами внедряете IDisposable).

Ответ 14

Далее создаются методы расширения для класса ReaderWriterLockSlim, которые позволяют выполнять следующие действия:

var rwlock = new ReaderWriterLockSlim();
using (var l = rwlock.ReadLock())
{
     // read data
}
using (var l = rwlock.WriteLock())
{
    // write data
}

Здесь код:

static class ReaderWriterLockExtensions() {
    /// <summary>
    /// Allows you to enter and exit a read lock with a using statement
    /// </summary>
    /// <param name="readerWriterLockSlim">The lock</param>
    /// <returns>A new object that will ExitReadLock on dispose</returns>
    public static OnDispose ReadLock(this ReaderWriterLockSlim readerWriterLockSlim)
    {
        // Enter the read lock
        readerWriterLockSlim.EnterReadLock();
        // Setup the ExitReadLock to be called at the end of the using block
        return new OnDispose(() => readerWriterLockSlim.ExitReadLock());
    }
    /// <summary>
    /// Allows you to enter and exit a write lock with a using statement
    /// </summary>
    /// <param name="readerWriterLockSlim">The lock</param>
    /// <returns>A new object that will ExitWriteLock on dispose</returns>
    public static OnDispose WriteLock(this ReaderWriterLockSlim rwlock)
    {
        // Enter the write lock
        rwlock.EnterWriteLock();
        // Setup the ExitWriteLock to be called at the end of the using block
        return new OnDispose(() => rwlock.ExitWriteLock());
    }
}

/// <summary>
/// Calls the finished action on dispose.  For use with a using statement.
/// </summary>
public class OnDispose : IDisposable
{
    Action _finished;

    public OnDispose(Action finished) 
    {
        _finished = finished;
    }

    public void Dispose()
    {
        _finished();
    }
}

Ответ 15

Глупый мне. Там есть способ сделать это еще проще, сделав блокированные методы частью каждого экземпляра (вместо статического, как в предыдущем сообщении). Теперь я действительно предпочитаю это, потому что нет необходимости передавать `rwlMyLock_m 'в какой-либо другой класс или метод.

class StackOTest
{
    private delegate DateTime ReadLockMethod();
    private delegate void WriteLockMethod();

    static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
    private DateTime dtMyDateTime_m;
    public DateTime MyDateTime
    {
        get
        {
            return ReadLockedMethod(
                delegate () { return dtMyDateTime_m; }
            );
        }
        set
        {
            WriteLockedMethod(
                delegate () { dtMyDateTime_m = value; }
            );
        }
    }

    private DateTime ReadLockedMethod(ReadLockMethod method)
    {
        rwlMyLock_m.AcquireReaderLock(0);
        try
        {
            return method();
        }
        finally
        {
            rwlMyLock_m.ReleaseReaderLock();
        }
    }

    private void WriteLockedMethod(WriteLockMethod method)
    {
        rwlMyLock_m.AcquireWriterLock(0);
        try
        {
            method();
        }
        finally
        {
            rwlMyLock_m.ReleaseWriterLock();
        }
    }
}