«используя» заявление против «попробуй наконец» - PullRequest
68 голосов
/ 10 ноября 2008

У меня есть куча свойств, для которых я собираюсь использовать блокировки чтения / записи. Я могу реализовать их с помощью 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();
    }
}

Ответы [ 15 ]

84 голосов
/ 10 ноября 2008

Из MSDN, с использованием оператора (C # Reference)

Оператор 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.

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

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

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

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

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

8 голосов
/ 10 ноября 2008

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

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

Обратите внимание, что Microsoft теперь рекомендует 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;}, вы можете пересмотреть подход; с дополнительной информацией я уверен, что переполнение стека может помочь; -)

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

Лично я использую оператор C # «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() могло произойти значительное исключение, я бы изменил дизайн для этой ситуации. Честно говоря, я сомневаюсь, что это когда-нибудь случится.

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

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

«Связка свойств» и блокировка на уровне получателя и установщика свойства выглядит неправильно. Ваша блокировка слишком мелкая. При наиболее типичном использовании объектов вам нужно убедиться, что вы приобрели блокировку для доступа к больше одновременно к одному свойству. Ваш конкретный случай может быть другим, но я в этом сомневаюсь.

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

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

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

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

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

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

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

СУХОЙ говорит: второе решение. Первое решение дублирует логику использования блокировки, а второе - нет.

1 голос
/ 10 ноября 2008

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

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

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

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

0 голосов
/ 10 сентября 2013

Следующее создает методы расширения для класса 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();
    }
}
0 голосов
/ 26 марта 2012

На самом деле в вашем первом примере, чтобы сделать решения сопоставимыми, вы бы также внедрили здесь «IDisposable» и вызывали «Dispose» из блока «finally» вместо того, чтобы снимать блокировку напрямую. Тогда вы будете реализованы по принципу «яблоки к яблокам» (и MSIL) (MSIL будет одинаковым для обоих решений). Тем не менее, хорошая идея использовать «использование» из-за добавленной области видимости и потому, что Framework обеспечит правильное использование «IDisposable» (последнее менее выгодно, если вы внедряете «IDisposabe» самостоятельно).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...