2009-10-26 1 views
1

Существует такой объект, похожий на DB-соединение, который понадобится моему веб-приложению. Это довольно медленно создавать и будут использоваться редко, поэтому я хотел бы сохранить только один экземпляр этого объекта. Если несколько запросов нуждаются в этом одновременно, они будут lock() объекта и сериализуют их доступ.Как создать потокобезопасный единственный экземпляр IDisposable?

Чтобы сделать вещи более увлекательными, объект является IDisposable и может быть закрыт. Естественно, я избегу писать код, который его закрывает, но ... вы знаете ... лучше безопасно, чем жаль, не так ли?

Таким образом, наивный подход был бы реализовать это что-то вроде этого:

private static DBClass _Instance; 
private static object _DBLock = new object(); 

public DBClass GetDB() 
{ 
    if (_Instance == null) 
     lock (_DBLock) 
      if (_Instance == null) 
       _Instance = CreateInstance(); 
    lock (_Instance) 
     if (_Instance.Disposed) 
      _Instance = CreateInstance(); 
    return _Instance; 
} 

Он будет использоваться как:

lock (var Conn = Global.GetDB()) 
{ 
    // Use Conn here. 
} 

Это вероятно, будет работать большую часть времени, но я см. открытие, которое может быть использовано - если два потока называют это одновременно, они получают один и тот же экземпляр одновременно, а затем все еще могут быть Dispose(), прежде чем другой приобретет на нем lock(). Таким образом - более поздний код выходит из строя. Проверка на Disposed в каждом месте, где она используется, также кажется неудобной. Фактически, lock() ing тоже кажется неудобным, но экземпляр не является по сути потокобезопасным, поэтому я ничего не могу с этим поделать.

Как вы это реализуете?

ответ

2

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

Учитывая, что вы не хотите, чтобы фактически располагал объект, есть ли причина, чтобы не обернуть его чем-то, что реализует тот же API, но не подвергает его удалению? Таким образом, вы можете также инкапсулировать блокировку, потенциально, в зависимости от того, как вы на самом деле используете объект. Другой способ сделать это - разоблачить API в терминах Action<DBClass> - вы передадите действие, которое хотите взять с объектом, и помощник позаботится о создании экземпляра и блокировки соответствующим образом.

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

+0

Проблема заключается в том, что эта вещь возвращает другие объекты, которые содержат ссылку на нее и выполняют операции над ней. Если я не закрою пару сотен классов, это не сработает. Подход Action <> заслуживает внимания, но мне интересно, стоит ли ему увеличивать беспорядок в коде. Мне нужен элегантный подход, но это похоже на другой путь ... –

+0

Кстати. Я все равно использую lock(). Почему нужна летучесть? –

+1

Возможно, из-за * вторичной блокировки, вы на самом деле все в порядке, но, учитывая, что у вас есть две разные блокировки, мне совершенно не ясно, что это действительно * хорошо. Я лично использовал бы единую блокировку для всего - поместите весь метод в оператор блокировки, используя '_DBLock'. Тонкости модели памяти довольно сложны. Аналогично, если другой поток использует объект * без * блокировки, но не располагает, нет никакой гарантии, что вы его заметите и создадите новый экземпляр. По существу, похоже, что этот дизайн открывает много возможностей для множества тонких ошибок. –

 Смежные вопросы

  • Нет связанных вопросов^_^