IDisposableのスレッドセーフな単一インスタンスを作成する方法は?
-
06-07-2019 - |
質問
このWebアプリケーションに必要なDB接続のようなオブジェクトがあります。作成にかなり時間がかかり、めったに使用されないので、そのインスタンスを1つだけ保持したいと思います。複数のリクエストが同時にそれを必要とする場合、それらはオブジェクトをlock()
アクセスし、アクセスをシリアル化します。
より楽しくするために、オブジェクトはIDisposableであり、閉じることができます。当然、それを閉じるコードを書くことは避けますが、...ご存じのように...申し訳ありませんが、より安全ですよね
したがって、na <!>#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.
}
これはおそらくほとんどの場合機能しますが、悪用される可能性のある開口部が表示されます-2つのスレッドが同時にこれを呼び出すと、同じインスタンスが同時に取得され、Dispose()
他方がDisposed
を取得する前に。したがって-後のコードは失敗します。 <=>を使用するすべての場所で<=>をチェックすることも厄介なようです。実際、<=>それも厄介なようですが、インスタンスは本質的にスレッドセーフではないため、私はそれについて何もできません。
これをどのように実装しますか?
解決
最初は、ダブルチェックロックを避けることをお勧めします。間違ってしまうのはとても簡単です-この場合、変数をvolatileにしないことでやったように。
実際にオブジェクトを破棄したくないということを考えると、同じAPIを実装するものにオブジェクトをラップせず、破棄を公開しない理由はありますか?そうすれば、オブジェクトの実際の使用方法に応じて、潜在的にロックをカプセル化することもできます。これを行う別の方法は、Action<DBClass>
の観点からAPIを公開することです。オブジェクトで実行するアクションを渡すと、ヘルパーがインスタンスの作成と適切なロックを処理します。
最後の1つのポイント:これらはすべて、テスト容易性の面でやや扱いにくいと感じています。あなたがユニットテストなどのファンであるかどうかはわかりませんが、シングルトンはしばしばテストをやや厄介にします。