Question

En écrivant un code fileté, je me sers de la classe ReaderWriterLockSlim pour gérer l'accès synchronisé aux variables. Faire cela, j'ai remarqué que j'écrivais toujours blocs try-enfin, la même pour chaque méthode et propriété.

En voyant l'occasion d'éviter de me répéter et résumer ce comportement que je construit une classe, ReaderWriterLockSection, destiné à être utilisé comme emballage mince à la serrure qui peut être utilisé avec la syntaxe du bloc C # using.

La classe est principalement comme suit:

public enum ReaderWriterLockType
{
   Read,
   UpgradeableRead,
   Write
}

public class ReaderWriterLockSection : IDisposeable
{
   public ReaderWriterLockSection(
       ReaderWriterLockSlim lock, 
       ReaderWriterLockType lockType)
   {
       // Enter lock.
   }

   public void UpgradeToWriteLock()
   {
       // Check lock can be upgraded.
       // Enter write lock.
   }

   public void Dispose()
   {
       // Exit lock.
   }
}

J'utilise la section comme suit:

private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

public void Foo()
{
    using(new ReaderWriterLockSection(_lock, ReaderWriterLockType.Read)
    {
        // Do some reads.
    }
}

Pour moi, cela semble être une bonne idée, qui rend mon code plus facile à lire et apparemment plus robuste que je ne vais pas oublier jamais de libérer un verrou.

Quelqu'un peut-il voir un problème avec cette approche? Y at-il raison pour cela est une mauvaise idée?

Était-ce utile?

La solution

Eh bien, il semble bien pour moi. Eric Lippert a déjà écrit sur les dangers de l'utilisation Dispose pour les scénarios « non-ressources », mais je pense que cela compterait comme une ressource.

Il peut rendre la vie difficile dans des scénarios de mise à niveau, mais vous pouvez toujours revenir à un peu plus manuel du code à ce moment-là.

Une autre alternative consiste à écrire une seule acquisition de verrouillage / utilisation / méthode de libération et de fournir l'action à prendre tout en maintenant le verrou en tant que délégué.

Autres conseils

Je me laisse aller habituellement dans ce genre de confections de code-sucré!

Voici une variante qui est un peu plus facile à lire pour les utilisateurs, au-dessus de votre API

public static class ReaderWriterLockExt{
  public static IDisposable ForRead(ReaderWriterLockSlim rwLock){
    return new ReaderWriterLockSection(rwLock,ReaderWriterLockType.Read);
  }
  public static IDisposable ForWrite(ReaderWriterLockSlim rwLock){
    return new ReaderWriterLockSection(rwLock,ReaderWriterLockType.Write);
  }
  public static IDisposable ForUpgradeableRead(ReaderWriterLockSlim wrLock){
    return new ReaderWriterLockSection(rwLock,ReaderWriterLockType.UpgradeableRead);
  }
}

public static class Foo(){
  private static readonly ReaderWriterLockSlim l=new ReaderWriterLockSlim(); // our lock
  public static void Demo(){


    using(l.ForUpgradeableRead()){ // we might need to write..

      if(CacheExpires()){   // checks the scenario where we need to write

         using(l.ForWrite()){ // will request the write permission
           RefreshCache();
         } // relinquish the upgraded write
      }

      // back into read mode
      return CachedValue();
    } // release the read 
  }
}

Je recommande également d'utiliser une variante qui prend un délégué d'action qui est invoquée lorsque le verrouillage ne peut être obtenue pendant 10 secondes, que je vais laisser comme un exercice au lecteur.

Vous pouvez également vérifier une RWL nulle dans les méthodes d'extension statiques, et assurez-vous que le verrou existe lorsque vous disposez il.

Cordialement, Florian

Il y a une autre considération ici, vous résolvez peut-être un problème que vous ne devriez pas résoudre. Je ne vois pas le reste de votre code, mais je peux deviner de vous voir valeur dans ce schéma.

Votre approche permet de résoudre un problème que si le code qui lit ou écrit la ressource partagée génère une exception. Implicite est que vous ne gérez pas l'exception dans la méthode qui fait la lecture / écriture. Si vous avez fait, vous pouvez simplement libérer le verrou dans le code de gestion des exceptions. Si vous ne gérez pas l'exception du tout, le fil va mourir de l'exception non gérée et votre programme prendra fin. Aucun point de libération d'un verrou alors.

Donc, il y a une clause de capture quelque part plus bas dans la pile d'appels qui intercepte l'exception et gère. Ce code doit restaurer l'état du programme afin qu'il puisse continuer de sens sans générer de mauvaises données ou mourir autrement en raison des exceptions causées par l'état modifié. Ce code a un travail difficile à faire. Il a besoin de deviner en quelque sorte la quantité de données lues ou écrites sans aucun contexte. Se tromper, ou seulement en partie raison, est potentiellement très déstabilisant pour l'ensemble du programme. Après tout, il était une ressource partagée, d'autres threads de lecture / écriture de celui-ci.

Si vous savez comment faire, puis par tous les moyens d'utiliser ce modèle. Tu ferais mieux de tester le diable hors de bien. Si vous n'êtes pas sûr alors d'éviter de gaspiller les ressources du système sur un problème que vous ne pouvez pas fiable corriger.

Une chose que je vous suggère quand envelopper une serrure pour faciliter la « utilisation » modèle est d'inclure un champ « danger d'état » dans la serrure; avant d'autoriser un code pour entrer dans la serrure, le code doit vérifier l'état de danger. Si l'état de danger est défini, et le code qui tente d'entrer dans le verrou n'a pas passé un paramètre spécial en disant qu'il est attendu qu'il pourrait être, la tentative d'acquérir le verrou devrait lancer une exception. Code qui va mettre temporairement la ressource gardée dans un mauvais état doit définir l'indicateur d'état de danger, faire ce qui doit être fait, puis réinitialiser l'indicateur d'état de danger une fois que l'opération est terminée et l'objet est dans un état sûr.

Si une exception se produit alors que l'indicateur d'état de danger, le verrouillage doit être libéré, mais l'indicateur d'état de danger doit rester fixé. Cela garantira que le code qui veut accéder à la ressource constatera que la ressource est corrompu, plutôt que d'attendre toujours pour le verrou soit relâché (ce qui serait le résultat s'il n'y avait pas « utiliser » ou « try-finally » bloc ).

Si le verrou étant enveloppé est un ReaderWriterLock, il peut être pratique d'avoir l'acquisition d'un « écrivain » REGL.VERROU automatiquement l'état de danger; malheureusement, il n'y a aucun moyen pour un IDisposable utilisé par un bloc using pour déterminer si le bloc est sorti proprement ou par exception. Par conséquent, je ne sais pas comment utiliser quelque chose comme un syntaxiquement « utilisant » bloc pour garder le drapeau « état de danger ».

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top