50 votes

Sécurité du filetage C # avec get / set

C'est un détail question pour C#.

Supposons que j'ai une classe avec un objet, et cet objet est protégé par une serrure:

Object mLock = new Object();
MyObject property;
public MyObject MyProperty {
    get {
         return property;
    }
    set { 
         property = value; 
    }
}

Je tiens un bureau de vote thread pour être en mesure d'interroger cette propriété. Je veux aussi que le fil de mise à jour des propriétés de cet objet, de temps en temps, et parfois, l'utilisateur peut mettre à jour la propriété, et que l'utilisateur veut être en mesure de voir que la propriété.

Sera le code suivant verrouiller correctement les données?

Object mLock = new Object();
MyObject property;
public MyObject MyProperty {
    get {
         lock (mLock){
             return property;
         }
    }
    set { 
         lock (mLock){
              property = value; 
         }
    }
}

Par "correctement", je veux dire, si je veux appeler

MyProperty.Field1 = 2;

ou quoi que ce soit, le champ sera verrouillé pendant que je fais la mise à jour? Est le paramètre qui est fait par l'opérateur = à l'intérieur de la portée de l' 'get' de la fonction, ou le 'get' de la fonction (et donc de la serrure) terminer premier, puis le réglage, puis " set " est appelée, contournant ainsi la serrure?

Edit: Depuis ce qui, apparemment, ne suffit pas, ce qui le fera? Ai-je besoin de faire quelque chose comme:

Object mLock = new Object();
MyObject property;
public MyObject MyProperty {
    get {
         MyObject tmp = null;
         lock (mLock){
             tmp = property.Clone();
         }
         return tmp;
    }
    set { 
         lock (mLock){
              property = value; 
         }
    }
}

ce qui est plus ou moins juste fait en sorte que je n'ai accès à une copie, ce qui signifie que si je devais avoir deux threads appeler un " get " dans le même temps, ils auraient chacun commencent avec la même valeur de Champ1 (à droite?). Est-il un moyen de faire lire et écrire de verrouillage sur une propriété qui a du sens? Ou devrais-je viens de me contraindre à la fermeture de sections de fonctions plutôt que les données lui-même?

Juste pour que cet exemple prend tout son sens: Monobjet est un pilote de périphérique qui retourne l'état de manière asynchrone. J'ai envoyer des commandes via un port série, puis le dispositif répond à ces commandes dans son propre temps doux. Maintenant, j'ai un fil qui interroge pour son statut ("Êtes-vous encore là? Pouvez-vous accepter des commandes?"), un thread qui attend des réponses sur le port série ("je Viens de recevoir le statut de chaîne 2, tout est bon"), et puis le thread d'INTERFACE utilisateur qui prend en d'autres commandes ("Utilisateur veut vous faire cette chose.") et des postes, les réponses du pilote ("je viens de faire la chose, maintenant mise à jour de l'INTERFACE utilisateur avec qui"). C'est pourquoi je veux verrou sur l'objet lui-même, plutôt que les champs de l'objet; ce serait un grand nombre de verrous, a et b, et pas à chaque appareil de cette classe a le même comportement, il suffit de comportement, de sorte que j'aurais du code individuelle de la part des dialogues si je individualisée les verrous.

39voto

LukeH Points 110965

Non, votre code ne sera pas verrouiller l'accès aux membres de l'objet renvoyé de MyProperty. Il verrouille uniquement MyProperty lui-même.

Votre exemple d'utilisation est vraiment deux opérations roulé dans un, équivaut à peu près à ceci:

// object is locked and then immediately released in the MyProperty getter
MyObject o = MyProperty;

// this assignment isn't covered by a lock
o.Field1 = 2;

// the MyProperty setter is never even called in this example

En bref - si deux threads accèdent MyProperty simultanément, la lecture sera brièvement bloqué la deuxième thread jusqu'à ce qu'il retourne l'objet pour le premier thread, mais il va ensuite retourner l'objet de la deuxième fil ainsi. Les deux fils aura alors complet, débloqué l'accès à l'objet.

EDIT en réponse à plus de détails dans la question

Je ne suis pas encore 100% sûr de ce que vous essayez d'atteindre, mais si vous voulez juste atomique accès à l'objet n'avez-vous pas de l'appeler le code de verrouillage à l'encontre de l'objet lui-même?

// quick and dirty example
// there's almost certainly a better/cleaner way to do this
lock (MyProperty)
{
    // other threads can't lock the object while you're in here
    MyProperty.Field1 = 2;
    // do more stuff if you like, the object is all yours
}
// now the object is up-for-grabs again

Pas l'idéal, mais à condition que tous les accès à l'objet est contenu dans lock (MyProperty) sections, cette approche va être thread-safe.

14voto

Hans Passant Points 475940

La programmation simultanée serait assez facile si votre approche pourrait fonctionner. Mais il ne le fait pas, l'iceberg qui s'enfonce que Titanic est, par exemple, le client de votre classe pour ce faire:

objectRef.MyProperty += 1;

La lecture-modification-écriture de la course est assez évident, y a pire, ceux. Il n'y a absolument rien que vous pouvez faire pour rendre votre propriété thread-safe, autres que de le rendre immuable. C'est votre client qui a besoin de traiter avec le mal de tête. Ne pas être en mesure de déléguer ce genre de responsabilité est le tendon d'Achille talon de la programmation simultanée.

5voto

Matt Davis Points 22019

Comme d'autres l'ont souligné, une fois de retour de l'objet à partir de la lecture, vous perdez le contrôle sur l'accès à l'objet et à quel moment. Pour faire ce que vous êtes désireux de le faire, vous aurez besoin de mettre un verrou à l'intérieur de l'objet lui-même.

Peut-être que je ne comprends pas l'image complète, mais en fonction de votre description, il ne sonne pas comme vous l'auriez besoin nécessairement d'avoir un verrou pour chaque champ. Si vous avez un ensemble de champs sont simplement lus et écrits par les getters et setters, vous pouvez probablement vous en sortir avec une seule serrure pour ces champs. Il est de toute évidence le potentiel que vous aurez inutilement sérialiser le fonctionnement de votre threads de cette façon. Mais encore une fois, selon votre description, il ne sonne pas comme vous êtes agressive accéder à l'objet.

Je vous suggère aussi d'utiliser un événement au lieu d'utiliser un thread pour interroger l'état de l'appareil. Avec le mécanisme d'interrogation, vous allez être frappé dans la serrure chaque fois que le thread des requêtes de l'appareil. Avec le mécanisme d'événements, une fois que les changements de statut, l'objet de notifier tout les auditeurs. À ce stade, votre "interrogation" thread (qui ne serait plus d'interrogation) se réveille et obtenir le nouveau statut. Ce sera beaucoup plus efficace.

Comme un exemple...

public class Status
{
    private int _code;
    private DateTime _lastUpdate;
    private object _sync = new object(); // single lock for both fields

    public int Code
    {
        get { lock (_sync) { return _code; } }
        set
        {
            lock (_sync) {
                _code = value;
            }

            // Notify listeners
            EventHandler handler = Changed;
            if (handler != null) {
                handler(this, null);
            }
        }
    }

    public DateTime LastUpdate
    {
        get { lock (_sync) { return _lastUpdate; } }
        set { lock (_sync) { _lastUpdate = value; } }
    }

    public event EventHandler Changed;
}

Votre "interrogation" fil ressemblerait à quelque chose comme ça.

Status status = new Status();
ManualResetEvent changedEvent = new ManualResetEvent(false);
Thread thread = new Thread(
    delegate() {
        status.Changed += delegate { changedEvent.Set(); };
        while (true) {
            changedEvent.WaitOne(Timeout.Infinite);
            int code = status.Code;
            DateTime lastUpdate = status.LastUpdate;
            changedEvent.Reset();
        }
    }
);
thread.Start();

2voto

headsling Points 484

Le verrouillage du champ d'application dans votre exemple, dans la mauvaise place, - il doit être à la portée de la "Monobjet" classe de la propriété plutôt que de son conteneur.

Si le MyObject ma classe d'objet est simplement utilisé pour contenir des données qu'un seul thread veut écrire, et l'autre (le thread d'INTERFACE utilisateur) pour lire à partir de là vous ne pourriez pas besoin d'un setter à tous et à construire qu'une seule fois.

Voir également si de placer des verrous au niveau de la propriété est l'écriture au niveau de l'écluse de granularité; si plus d'une propriété peut être écrite dans le but de représenter l'état d'une transaction (par exemple: nombre total d'ordres et de poids total) alors il peut être préférable d'avoir de l'écluse à la MyObject niveau (c'est à dire de verrouillage( myObject.SyncRoot ) ... )

1voto

SoapBox Points 14183

Dans l'exemple de code que vous avez posté, un get n'est jamais exécuté.

Dans un exemple plus compliqué:

MyProperty.Field1 = MyProperty.doSomething() + 2;

Et bien sûr, en supposant que vous avez un:

lock (mLock) 
{
    // stuff...
}

En doSomething() puis tous les verrouiller les appels seraient pas suffisantes pour assurer la synchronisation sur l'ensemble de l'objet. Dès que l' doSomething() retours de fonction, le verrou est perdu, ensuite, l'ajout est fait, et puis la cession arrive, qui se verrouille de nouveau.

Ou, à l'écrire d'une autre façon vous pouvez faire semblant comme les serrures ne sont pas fait amutomatically, et de réécrire ce plus comme "code machine" avec une opération par ligne, et il devient évident:

lock (mLock) 
{
    val = doSomething()
}
val = val + 2
lock (mLock)
{
    MyProperty.Field1 = val
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X