Как создать потокобезопасный единственный экземпляр IDisposable?
-
06-07-2019 - |
Вопрос
Этот объект, похожий на соединение с БД, понадобится моему веб-приложению. Он довольно медленный для создания и будет использоваться редко, поэтому я хотел бы сохранить только один его экземпляр. Если это требуется нескольким запросам одновременно, они будут lock()
объект и сериализовать свой доступ.
Чтобы сделать вещи более увлекательными, объект является IDisposable и может быть закрыт. Естественно, я буду избегать написания кода, который закрывает его, но ... вы знаете ... лучше, чем потом сожалеть, верно?
Итак, наивный & # 239; ve подход заключается в том, чтобы реализовать его примерно так:
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()
это прежде, чем другой приобретет Disposed
на этом. Таким образом - позже код не работает. Проверка на <=> в любом месте, где он используется, также кажется неудобной. На самом деле, <=> это тоже кажется неуклюжим, но этот экземпляр по своей сути не ориентирован на многопоточность, поэтому я ничего не могу с этим поделать.
Как бы вы это реализовали?
Решение
Для начала я бы рекомендовал избегать двойной проверки блокировки. Очень легко ошибиться - как вы и сделали в этом случае, не делая переменную volatile.
Учитывая, что вы не хотите на самом деле утилизировать объект, есть ли причина не заключать его в нечто, реализующее тот же API, но без раскрытия удаления? Таким образом, вы также можете инкапсулировать блокировку, в зависимости от того, как вы на самом деле используете объект. Другой способ сделать это - представить API в терминах Action<DBClass>
- вы передаете действие, которое хотите выполнить с объектом, а помощник позаботится о создании экземпляра и соответствующей блокировке.
И последнее замечание: все это кажется несколько сложным с точки зрения тестируемости. Я не знаю, являетесь ли вы поклонником модульного тестирования и т. Д., Но одиночные игры часто затрудняют тестирование.