web-dev-qa-db-fra.com

Déclaration de verrouillage C #, sur quel objet verrouiller?

J'ai 3 questions pour lesquelles j'ai besoin d'aide.

  1. Quels sont les objets/références corrects à transmettre en tant que paramètre d'instruction lock? J'ai vu beaucoup d'exemples de codes et j'ai remarqué que les objets/références passés pourraient éventuellement ne pas être liés à la classe actuelle ni à aucune autre classe du programme tant que le modificateur d'accès static n'est pas public. ? Par exemple.:

    private Object anyObj = new Object();
    lock(anyObj){.....}
    
    private static readonly object Locker = new object();
    lock(Locker){.....}
    

    Cela n'a aucun sens pour moi.

  2. J'ai trouvé un exemple de code dans MSDN sur le multi-threading qui utilise également les instructions lock. Dans l'exemple, il y a deux blocs try/catch contenant le Monitor.Wait(). Si je comprends bien la logique, le readerFlag interdira au programme de saisir le bloc try/catch.
    Le code est l'exemple 2 à partir d'ici:
    http://msdn.Microsoft.com/en-us/library/aa645740 (v = vs.71) .aspx

  3. Comment exécuter un thread qui s'exécute en arrière-plan tant que le Windows Form est actif?

52
user1885498

Comment et ce que vous verrouillez dépend de ce que vous faites.

Disons que vous travaillez avec un appareil quelconque - disons une cafetière. Vous pourriez avoir une classe qui ressemble à ceci:

public CoffeeMaker {
    private IntPtr _coffeeHandle;
    private Object _lock = new Object();
}

Dans ce cas, vous protégez l'accès à _coffeeHandle - un pointeur/handle vers un périphérique physique réel, c'est donc assez simple:

public int AvailableCups {
    get {
        lock (_lock) {
            return GetAvailableCups(_coffeeHandle); // P/Invoked
        }
    }
}

public void Dispense(int nCups)
{
    lock (_lock) {
        int nAvail = GetAvailableCups(_coffeeHandle);
        if (nAvail < nCups) throw new CoffeeException("not enough coffee.");
        Dispense(_coffeeHandle, nCups); // P/Invoked
    }
 }

Donc, si j'utilise une application multithread, je ne veux (probablement) pas lire le nombre de gobelets disponibles pendant la distribution (c'est peut-être une erreur matérielle). En protégeant les accès à la poignée, je peux m'en assurer. De plus, on ne peut pas me demander de dispenser pendant que je le fais déjà - ce serait mauvais, alors c'est également protégé. Enfin, je ne dispense que si j'ai assez de café à disposition et vous remarquez que je n'utilise pas ma propriété publique pour vérifier cela - ainsi les mesures visant à garantir qu'il y a suffisamment de café et la distribution sont liées. Le mot magique est atomique - ils ne peuvent pas être séparés sans créer de problèmes.

Vous utilisez un objet statique comme verrou si vous ne possédez qu'une seule instance d'une ressource à protéger. Pensez, "ai-je un singleton?" et ce sera un guide pour quand vous pourriez avoir besoin d'un verrou statique. Par exemple, supposons que CoffeeMaker ait un constructeur privé. Au lieu de cela, vous avez une méthode d'usine qui construit des machines à café:

static Object _factLock = new Object();

private CoffeeMaker(IntPtr handle) { _coffeeHandle = handle; }

public static CoffeeMaker GetCoffeeMaker()
{
    lock (_factLock) {
        IntPtr _handle = GetCoffeeMakerHandle(); // P/Invoked
        if (_handle == IntPtr.Zero) return null;
        return new CoffeeMaker(_handle);
    }
 }

Maintenant, dans ce cas, il semble que CoffeeMaker devrait mettre en œuvre IDisposable pour que cette poignée soit traitée, car si vous ne le relâchez pas, il est possible que quelqu'un ne reçoive pas son café.

Il y a cependant quelques problèmes - peut-être que s'il n'y a pas assez de café, nous devrions en faire plus - et cela prend beaucoup de temps. Heck - la distribution du café prend beaucoup de temps, c'est pourquoi nous veillons à protéger nos ressources. Maintenant, vous pensez que tout ce qu’est la cafetière devrait être un fil conducteur et qu’il devrait y avoir un événement qui se déclenche lorsque le café est terminé, puis cela commence à se compliquer et vous comprenez l’importance de savoir ce que vous verrouillez et à quel moment pour ne pas bloquer le café parce que vous avez demandé combien de tasses y sont.

Et si les mots "impasse", "atomique", "moniteur", "attente" et "impulsion" vous semblent tous étrangers, vous devriez envisager de lire sur le multitraitement/le multithreading en général et de voir si vous pouvez le résoudre le problème des salons de coiffure équitables ou le problème des philosophes dînants , les deux exemples par excellence de conflits de ressources.

74
plinth

1) votre code est incomplet. Vous verrouillez toujours une certaine ressource (partagée). Le anyObject devrait avoir une correspondance étroite 1-1 dans sa vie avec cet objet partagé.

Par exemple:

a) le modèle simple mais le plus direct:

List<MyClass> sharedList = ...;
...
lock (sharedList) { sharedList.Add(item); }

ce modèle présente un inconvénient: que se passe-t-il si un autre code se verrouille également sur sharedList pour d'autres raisons? Ce n’est généralement pas un problème pratique, mais c’est la raison pour laquelle le modèle recommandé est (b):

List<MyClass> sharedList = ...;
private object listLock = new object();
...
lock (listLock) { sharedList.Add(item); }

Ou, lorsque l'objet partagé est statique (c):

static List<MyClass> sharedList = ...;
private static object listLock = new object();
...
lock (listLock) { sharedList.Add(item); }

2) Les threads alternent le paramètre readerFlag sur true ou false afin que les blocs try/catch soient entrés. La synchronisation est effectuée avec Monitor.Pulse () et .Wait (). Notez que Wait () donnera le verrou pour la durée s, il n'y a pas d'interblocage.

33
Henk Holterman

1: l'objet que vous utilisez définit/est défini par la granularité de verrouillage que vous essayez d'appliquer. Si is est "tout ce qui appelle contre l'instance actuelle", un private readonly object syncLock = new object() serait raisonnable. S'il s'agit de "n'importe quel code, quelle que soit l'instance" (statique, en particulier), alors private readonly static object syncLock = new object(). Parfois, vous tentez de protéger une "chose" évidente qui servira également: une liste, une file d'attente, etc. Les principales décisions fausses sont: this, typeof(...), any string, tout type de valeur que vous boxez pour chaque lock et tout ce que vous avez divulgué en dehors de l'instance.

2: Monitor.Waitreleases les verrous du thread actuel, en attente d'un "Pulse" ou d'un délai d'attente, à quel moment il se réveille et rejoint la file d'attente pour regain aux verrous qu'il avait (notez le "s" qu'il y a pour la rentrée). Cela signifie que deux threads peuvent utiliser un signal Monitor pour entre eux, par impulsions et en attente.

3: non apparenté; mais fondamentalement "vérifier un drapeau périodiquement, et quand il est pulsé"

6
Marc Gravell

Selon le documentation MSDN :

L'argument fourni pour le mot clé lock ... est utilisé pour définir la portée du verrou. ... À proprement parler, l'objet fourni est utilisé uniquement pour identifier de manière unique la ressource partagée entre plusieurs threads. Il peut donc s'agir d'une instance de classe arbitraire. En pratique, cependant, cet objet représente généralement la ressource pour laquelle la synchronisation de thread est nécessaire.

Dans mon cas, j'ai transmis l'objet statique exact que je devais changer.

2
Vladimir T.