2009-10-26 7 views
1

Il y a cet objet semblable à une connexion DB dont mon application Web aura besoin. Il est assez lent à créer et sera rarement utilisé, donc je n'en garderai qu'une seule instance. Si plusieurs demandes en ont besoin en même temps, ils vont lock() l'objet et sérialiser leur accès.Comment créer une instance unique thread-safe d'un IDisposable?

Pour rendre les choses plus amusantes, l'objet est IDisposable et peut être fermé. Naturellement, j'éviterai d'écrire du code qui le ferme, mais ... tu sais ... mieux vaut prévenir que guérir, non?

Ainsi, l'approche naïve serait de mettre en œuvre quelque chose comme ceci:

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; 
} 

Il sera ensuite utilisé comme:

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

Cela fonctionnera probablement la plupart du temps, mais je voir une ouverture qui pourrait être exploitée - si deux threads l'appellent en même temps, ils obtiennent la même instance en même temps, et alors on pourrait encore l'Dispose() avant que l'autre n'obtienne un lock() dessus. Ainsi - le code plus tard échoue. Vérification de Disposed à chaque endroit qui l'utilise semble également gênant. En fait, cela semble gênant aussi, mais l'instance n'est pas intrinsèquement thread-safe donc je ne peux rien y faire.

Comment implémenteriez-vous ceci?

Répondre

2

Pour commencer, je suggère d'éviter le double contrôle de verrouillage. Il est très facile de se tromper - comme vous l'avez fait dans ce cas, en ne rendant pas la variable volatile. Étant donné que vous ne voulez pas effectivement disposer de l'objet, y at-il une raison de ne pas l'envelopper dans quelque chose qui implémente la même API mais sans exposer la disposition? De cette façon, vous pouvez également encapsuler le verrouillage, potentiellement, en fonction de la façon dont vous utilisez réellement l'objet. Une autre façon de le faire serait d'exposer une API en termes de Action<DBClass> - vous passez dans l'action que vous voulez prendre avec l'objet, et l'assistant prend soin de créer l'instance et de verrouiller de manière appropriée.

Un dernier point: tout cela est assez délicat en termes de testabilité. Je ne sais pas si vous êtes fan des tests unitaires, mais les singletons rendent souvent la vie un peu difficile à tester.

+0

Le problème est que cette chose retourne d'autres objets qui contiennent une référence à elle et effectuer des opérations sur elle. À moins d'envelopper quelques centaines de cours, ça ne marchera pas. L'approche d'Action <> est remarquable, mais je me demande si l'encombrement accru du code en vaut la peine. Je veux une approche élégante, mais c'est plutôt le contraire ... –

+0

Btw - J'utilise un verrou() de toute façon. Pourquoi le besoin de volatiles? –

+1

Il est possible qu'en raison du verrou * secondary *, vous soyez en règle - mais étant donné que vous avez deux verrous différents impliqués, il n'est pas du tout clair pour moi que ça soit * correct *. Personnellement, j'utiliserais un seul verrou pour tout - mettre toute la méthode dans une instruction de verrouillage en utilisant '_DBLock'. Les subtilités du modèle de mémoire sont assez délicates. De même, si un autre thread utilise l'objet * without * locking mais dispose, il n'y a aucune garantie que vous le repérerez et que vous créerez une nouvelle instance. Fondamentalement, il se sent comme ce design ouvre la place à beaucoup de bugs subtils. –

Questions connexes