Как создать потокобезопасный единственный экземпляр IDisposable? - PullRequest
1 голос
/ 26 октября 2009

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

Чтобы сделать вещи более увлекательными, объект является 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() это тоже кажется неловким, но этот экземпляр не является поточно-ориентированным, поэтому я ничего не могу с этим поделать.

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

1 Ответ

2 голосов
/ 26 октября 2009

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

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

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

...