web-dev-qa-db-fra.com

Un objet verrouillé reste-t-il verrouillé si une exception se produit à l'intérieur?

Dans une application de threading c #, si je devais verrouiller un objet, disons une file d'attente, et si une exception se produit, l'objet restera-t-il verrouillé? Voici le pseudo-code:

int ii;
lock(MyQueue)
{
   MyClass LclClass = (MyClass)MyQueue.Dequeue();
   try
   {
      ii = int.parse(LclClass.SomeString);
   }
   catch
   {
     MessageBox.Show("Error parsing string");
   }
}

Si je comprends bien, le code après la capture ne s'exécute pas - mais je me demandais si le verrou serait libéré.

75
Khadaji

Premier; avez-vous envisagé TryParse?

in li;
if(int.TryParse(LclClass.SomeString, out li)) {
    // li is now assigned
} else {
    // input string is dodgy
}

Le verrou sera libéré pour 2 raisons; d'abord, lock est essentiellement:

Monitor.Enter(lockObj);
try {
  // ...
} finally {
    Monitor.Exit(lockObj);
}

Seconde; vous interceptez et ne relancez pas l'exception interne, donc le lock ne voit jamais réellement d'exception. Bien sûr, vous maintenez le verrou pendant la durée d'un MessageBox, ce qui pourrait être un problème.

Donc, il sera publié dans toutes les exceptions irrécupérables catastrophiques, sauf les plus fatales.

81
Marc Gravell

Je note que personne n'a mentionné dans ses réponses à cette vieille question que libérer un verrou sur une exception est une chose incroyablement dangereuse à faire. Oui, verrouiller les instructions en C # ont une sémantique "enfin"; lorsque la commande quitte la serrure normalement ou anormalement, la serrure est libérée. Vous en parlez tous comme si c'était une bonne chose, mais c'est une mauvaise chose! La bonne chose à faire si vous avez une région verrouillée qui lève une exception non gérée est de terminer le processus malade immédiatement avant qu'il ne détruise plus de données utilisateur , pas libérer le verrou et continuer .

Regardez-le de cette façon: supposons que vous ayez une salle de bain avec une serrure à la porte et une file de personnes qui attendent à l'extérieur. Une bombe dans la salle de bain explose, tuant la personne qui s'y trouve. Votre question est "dans cette situation, la serrure sera-t-elle automatiquement déverrouillée pour que la prochaine personne puisse entrer dans la salle de bain?" Oui, il sera. Ce n'est pas une bonne chose. Une bombe vient de exploser et a tué quelqu'un! La plomberie est probablement détruite, la maison n'est plus structurellement saine et il pourrait y avoir une autre bombe à l'intérieur . La bonne chose à faire est de faire sortir tout le monde le plus rapidement possible et démolir toute la maison.

Je veux dire, réfléchissez bien: si vous avez verrouillé une région de code afin de lire à partir d'une structure de données sans qu'il soit muté sur un autre thread, et quelque chose dans cette structure de données a levé une exception, sont bonnes parce que la structure des données est corrompue . Les données utilisateur sont désormais gâchées; vous ne voulez pas essayer d'enregistrer les données utilisateur à ce stade car vous enregistrez corrompu données. Terminez simplement le processus.

Si vous avez verrouillé une région de code afin d'effectuer une mutation sans qu'un autre thread ne lise l'état en même temps, et que la mutation lève, alors si les données n'étaient pas corrompues auparavant, elles le sont maintenant. C'est exactement le scénario contre lequel le verrou est censé protéger . Maintenant, le code qui attend de lire cet état aura immédiatement accès à l'état corrompu, et se bloquera probablement lui-même. Encore une fois, la bonne chose à faire est de mettre fin au processus.

Peu importe comment vous le découpez, une exception à l'intérieur d'un verrou est les mauvaises nouvelles . La bonne question à poser n'est pas "est-ce que ma serrure sera nettoyée en cas d'exception?" La bonne question à se poser est "comment puis-je m'assurer qu'il n'y a jamais d'exception à l'intérieur d'un verrou? Et si c'est le cas, comment puis-je structurer mon programme pour que les mutations soient rétablies dans les bons états précédents?"

92
Eric Lippert

oui, cela sortira correctement; lock agit comme try/finally, avec la Monitor.Exit(myLock) dans le enfin, donc peu importe comment vous la quittez, elle sera libérée. En guise de note latérale, il est préférable d'éviter catch(... e) {throw e;}, car cela endommage la trace de pile sur e; il vaut mieux ne pas l'attraper pas du tout, ou bien: utilisez throw; plutôt que throw e; qui fait un re-throw.

Si vous voulez vraiment savoir, un verrou en C # 4/.NET 4 est:

{
    bool haveLock = false;
    try {
       Monitor.Enter(myLock, ref haveLock);
    } finally {
       if(haveLock) Monitor.Exit(myLock);
    }
} 
38
Marc Gravell

"Une instruction de verrouillage est compilée en un appel à Monitor.Enter, puis un bloc try… finally. Dans le bloc finally, Monitor.Exit est appelé.

La génération de code JIT pour x86 et x64 garantit qu'aucun abandon de thread ne peut se produire entre un appel Monitor.Enter et un bloc try qui le suit immédiatement. "

Tiré de: Ce site

13
GH.

Votre verrou sera libéré correctement. Un lock agit comme ceci:

try {
    Monitor.Enter(myLock);
    // ...
} finally {
    Monitor.Exit(myLock);
}

Et les blocs finally sont garantis pour s'exécuter, peu importe la façon dont vous quittez le bloc try.

5
Ry-

Juste pour ajouter un peu à l'excellente réponse de Marc.

De telles situations sont la raison même de l'existence du mot clé lock. Il aide les développeurs à s'assurer que le verrou est libéré dans le bloc finally.

Si vous êtes obligé d'utiliser Monitor.Enter/Exit par exemple pour prendre en charge un délai d'expiration, vous devez vous assurer de passer l'appel à Monitor.Exit dans le bloc finally pour assurer une libération correcte du verrou en cas d'exception.

4
Brian Rasmussen